diff --git a/.actrc b/.actrc new file mode 100644 index 000000000..9a0f250d0 --- /dev/null +++ b/.actrc @@ -0,0 +1,2 @@ +-P ubuntu-latest=node:22-bookworm +--container-options=--memory=32g --network=host diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..83342269d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +# Exclude large directories that aren't needed for build +vscode/node_modules + +# Exclude build artifacts +*.tar.gz +*.log + +# Exclude development files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Exclude OS files +.DS_Store +Thumbs.db diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..fc42ca9b9 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,109 @@ +name: Build + +on: + push: + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + # The main job for building the application + build: + name: Build sagemaker-code-editor + runs-on: ubuntu-latest + timeout-minutes: 180 + env: + DISABLE_V8_COMPILE_CACHE: 1 + + steps: + - name: Checkout repo with submodules + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y make gcc g++ libx11-dev xorg-dev libxkbfile-dev libsecret-1-dev libkrb5-dev python3 jq perl gettext automake autoconf quilt + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + + - name: Apply patches (if any) + run: | + if [ -d patches ] && [ "$(ls -A patches)" ]; then + { + quilt push -a --leave-rejects --color=auto + } || { + printf "\nPatching error, review logs!\n" + find ./vscode -name "*.rej" + exit 1 + } + fi + + - name: Set Development Version + id: version + run: | + SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7) + VERSION="0.0.0-dev-${SHORT_SHA}" + echo "VERSION=$VERSION" >> $GITHUB_ENV + echo "Generated version for this build: $VERSION" + + - name: Build vscode + run: | + cd vscode + export DISABLE_V8_COMPILE_CACHE=1 + export UV_THREADPOOL_SIZE=4 + + npm install -g node-gyp + + # Install dependencies using npm, skip optional and native modules + npm install + + # Run the gulp build task with memory optimizations + ARCH_ALIAS=linux-x64 + node --max-old-space-size=32768 --optimize-for-size \ + ./node_modules/gulp/bin/gulp.js \ + "vscode-reh-web-${ARCH_ALIAS}-min" + + - name: Find build output + id: find_output + run: | + BUILD_PATH=$(find . -name "vscode-reh-web-linux-x64" -type d | head -n 1) + if [ -z "$BUILD_PATH" ]; then + echo "::error::Build output directory 'vscode-reh-web-linux-x64' not found!" + exit 1 + fi + echo "Build output found at: $BUILD_PATH" + echo "build_path=$BUILD_PATH" >> $GITHUB_OUTPUT + + - name: Rename build output directory + id: rename_output + run: | + ORIG_PATH="${{ steps.find_output.outputs.build_path }}" + PARENT_DIR=$(dirname "$ORIG_PATH") + mv "$ORIG_PATH" "$PARENT_DIR/sagemaker-code-editor" + echo "Renamed build output directory to: $PARENT_DIR/sagemaker-code-editor" + echo "build_path=$PARENT_DIR/sagemaker-code-editor" >> $GITHUB_OUTPUT + + - name: Create tarball archive + run: | + TARBALL="sagemaker-code-editor-${{ env.VERSION }}.tar.gz" + BUILD_DIR_PATH="${{ steps.rename_output.outputs.build_path }}" + PARENT_DIR=$(dirname "$BUILD_DIR_PATH") + BUILD_DIR_NAME=$(basename "$BUILD_DIR_PATH") + echo "Creating '$TARBALL' from '$BUILD_DIR_NAME' in '$PARENT_DIR'" + tar czf $TARBALL -C "$PARENT_DIR" "$BUILD_DIR_NAME" + + - name: Upload build artifact + if: env.ACT == '' + uses: actions/upload-artifact@v4 + with: + name: npm-package + path: sagemaker-code-editor-${{ env.VERSION }}.tar.gz diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..0467d904d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,38 @@ +name: CI + +on: + push: + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + run-unit-tests: + name: Run unit tests + runs-on: ubuntu-latest + steps: + # Checkout repository code + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + + # Verify CSP line exists in target TypeScript file + - name: Check CSP configuration in webClientServer.ts + run: | + TARGET_FILE="patched-vscode/src/vs/server/node/webClientServer.ts" + REQUIRED_TEXT="'connect-src \'self\' ws: wss: https://main.vscode-cdn.net http://localhost:* https://localhost:* https://login.microsoftonline.com/ https://update.code.visualstudio.com https://*.vscode-unpkg.net/ https://default.exp-tas.com/vscode/ab https://vscode-sync.trafficmanager.net https://vscode-sync-insiders.trafficmanager.net https://*.gallerycdn.vsassets.io https://marketplace.visualstudio.com https://openvsxorg.blob.core.windows.net https://az764295.vo.msecnd.net https://code.visualstudio.com https://*.gallery.vsassets.io https://*.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com https://*.servicebus.windows.net/ https://vscode.blob.core.windows.net https://vscode.search.windows.net https://vsmarketplacebadges.dev https://vscode.download.prss.microsoft.com https://download.visualstudio.microsoft.com https://*.vscode-unpkg.net https://open-vsx.org;'" + + if [ ! -f "$TARGET_FILE" ]; then + echo "โŒ FAIL: Target file $TARGET_FILE does not exist." + exit 1 + fi + + if grep -F "$REQUIRED_TEXT" "$TARGET_FILE" > /dev/null; then + echo "โœ… PASS: Required CSP text exists." + else + echo "โŒ FAIL: Required CSP text NOT found in $TARGET_FILE" + exit 1 + fi \ No newline at end of file diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml new file mode 100644 index 000000000..f90e622fa --- /dev/null +++ b/.github/workflows/e2e.yml @@ -0,0 +1,38 @@ +name: E2E Tests + +on: + workflow_run: + workflows: ["Build"] + types: [completed] + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + e2e-tests: + name: E2E tests + runs-on: ubuntu-latest + if: github.event.workflow_run.conclusion == 'success' || github.event_name == 'pull_request' + steps: + - name: Checkout repo with submodules + uses: actions/checkout@v4 + with: + submodules: recursive + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + - name: Run E2E tests + run: | + # Add your actual E2E test commands here + echo "[PLACEHOLDER] Running E2E tests..." + # Example: npm run test:e2e + - uses: actions/upload-artifact@v4 + if: failure() + with: + name: e2e-test-results + path: test-results/ \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..daf663522 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,121 @@ +# Workflow name +name: Release + +# This workflow is triggered manually from the GitHub Actions tab. +on: + workflow_dispatch: + inputs: + version: + description: 'The release version (e.g., v1.8.0). This will be used to create the Git tag.' + required: true + type: string + # This input specifies the branch to tag and release from. + source_branch: + description: 'The branch to create the release from (e.g., main or 1.8). This branch MUST have the final code.' + required: true + type: string + default: 'main' + +jobs: + # The job for creating a release + create-release: + name: Create Release + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + # This permission is required for creating a release and uploading assets. + contents: write + + steps: + # Step 1: Check out the code from the SPECIFIED BRANCH in the AWS repository. + - name: Checkout code + uses: actions/checkout@v4 + with: + # This ensures we are on the correct branch to get the latest code. + ref: ${{ github.event.inputs.source_branch }} + # CRITICAL: We check out the code from the AWS repository directly. + repository: aws/sagemaker-code-editor + + # Step 2: Explicitly get the commit SHA of the checked-out branch HEAD. + - name: Get commit SHA + id: get_sha + run: echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + + # Step 3: Delete existing tag in the AWS repo if you want to re-run the release. + - name: Delete existing tag (if any) + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const tag = '${{ github.event.inputs.version }}'; + try { + await github.rest.git.deleteRef({ + owner: 'aws', + repo: 'sagemaker-code-editor', + ref: `tags/${tag}` + }); + console.log(`Deleted existing tag: ${tag}`); + } catch (e) { + if (e.status !== 404 && e.status !== 422) { + // Re-throw the error if it's not a "Not Found" or "Unprocessable" error + throw e; + } + console.log(`Tag ${tag} does not exist or already deleted.`); + } + + + # Step 4: Download the build artifact from the UPSTREAM repository after a PUSH event. + - name: Download artifact from build workflow + uses: dawidd6/action-download-artifact@v6 + with: + # CRITICAL: Explicitly specify the repository where the build artifact was created. + repo: aws/sagemaker-code-editor + # BEST PRACTICE: Look for artifacts created by a 'push' event (e.g., after a PR is merged). + event: push + workflow: build.yml + branch: ${{ github.event.inputs.source_branch }} + name: npm-package + path: ./release-assets + workflow_conclusion: success + + # Step 5: Prepare the release assets by renaming the artifact. + - name: Prepare release assets + id: prepare_assets + run: | + # Find the downloaded tarball (there should only be one). + ARTIFACT_FILE=$(find ./release-assets -name "*.tar.gz") + + if [ -z "$ARTIFACT_FILE" ]; then + echo "::error::Build artifact not found! Ensure a 'build.yml' workflow ran successfully on the '${{ github.event.inputs.source_branch }}' branch in 'aws/sagemaker-code-editor' after the code was pushed/merged." + exit 1 + fi + + # Get the version from the manual input, and remove the leading 'v' if it exists. + VERSION_TAG="${{ github.event.inputs.version }}" + VERSION_NUM="${VERSION_TAG#v}" + + # Create the new, clean filename for the release. + NEW_FILENAME="code-editor${VERSION_NUM}.tar.gz" + + # Rename the file. + mv "$ARTIFACT_FILE" "./release-assets/$NEW_FILENAME" + + echo "Renamed artifact to $NEW_FILENAME" + # Set the new filename as an output for the next step. + echo "filename=./release-assets/$NEW_FILENAME" >> $GITHUB_OUTPUT + + # Step 6: Create the GitHub Release in the AWS repo using the CORRECT commit SHA. + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + # We need a token with permissions to create releases in the AWS repo. + token: ${{ secrets.GITHUB_TOKEN }} + # CRITICAL: Explicitly specify the repository to create the release in. + repository: aws/sagemaker-code-editor + name: CodeEditor ${{ github.event.inputs.version }} + tag_name: ${{ github.event.inputs.version }} + files: ${{ steps.prepare_assets.outputs.filename }} + draft: false + generate_release_notes: false + # CRITICAL: Force the tag to be created on the commit we explicitly got in Step 2. + target_commitish: ${{ steps.get_sha.outputs.sha }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index eab08afe3..b5e3fb045 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .DS_Store -.pc \ No newline at end of file +.pc +.artifacts +bin \ No newline at end of file diff --git a/Development.md b/Development.md new file mode 100644 index 000000000..d6e581058 --- /dev/null +++ b/Development.md @@ -0,0 +1,225 @@ +# SageMaker Code Editor - Development Guide + +This guide explains how to develop and test SageMaker Code Editor locally using the separated CI/CD pipeline. + +## ๐Ÿš€ Quick Start + +### Prerequisites +- Node.js 20+ +- Docker (for act testing) +- Git with submodules + +### Local Development +```bash +# Test your changes locally +./scripts/test-local.sh + +# Clean cache when needed +./scripts/clean-cache.sh +``` + +### CI/CD Testing +```bash +# Test unit tests locally +~/bin/act -W .github/workflows/ci.yml + +# Test build (may hit memory limits locally) +~/bin/act -W .github/workflows/build.yml --container-options="--memory=8g" +``` + +## ๐Ÿ“‹ Development Workflow + +### 1. Make Changes +Edit your code, patches, or configuration files. + +### 2. Test Locally +```bash +./scripts/test-local.sh +``` +- **First run**: 15-20 minutes (full build) +- **Subsequent runs**: ~30 seconds (cached) +- **Server**: http://localhost:8080 + +### 3. Validate with CI Tests +```bash +# Fast unit tests (30 seconds) +~/bin/act -W .github/workflows/ci.yml +``` + +### 4. Push to GitHub +```bash +git add . +git commit -m "Your changes" +git push origin your-branch +``` + +## ๐Ÿ› ๏ธ Tools Reference + +### test-local.sh +**Purpose**: Build and run SageMaker Code Editor locally with smart caching. + +**Usage**: +```bash +./scripts/test-local.sh +``` + +**What it does**: +1. Installs dependencies (cached) +2. Applies patches +3. Builds VS Code (cached by git revision + patches) +4. Starts server on http://localhost:8080 + +**Caching**: +- Dependencies cached by `package.json` changes +- VS Code builds cached by git revision + patch changes +- Cache stored in `.local-cache/` directory + +**First run**: 15-20 minutes +**Cached runs**: ~30 seconds + +### clean-cache.sh +**Purpose**: Clean all local caches and build artifacts. + +**Usage**: +```bash +./scripts/clean-cache.sh +``` + +**What it removes**: +- `.local-cache/` directory +- `node_modules/` directories +- `vscode-reh-web-linux-x64/` build output + +**When to use**: +- Build issues or corruption +- Major dependency changes +- Disk space cleanup + +### act (GitHub Actions locally) +**Purpose**: Test GitHub Actions workflows locally using Docker. + +**Installation**: +```bash +curl -s https://raw.githubusercontent.com/nektos/act/master/install.sh | bash -s -- -b ~/bin +``` + +**Usage**: +```bash +# Test CI workflow (recommended) +~/bin/act -W .github/workflows/ci.yml + +# Test specific job +~/bin/act -j run-unit-tests -W .github/workflows/ci.yml + +# Test build workflow (memory intensive) +~/bin/act -W .github/workflows/build.yml --container-options="--memory=8g" + +# List available workflows +~/bin/act --list +``` + +**Limitations**: +- Build workflow may hit Docker memory limits locally +- Use for CI validation, not full builds +- GitHub Actions runners have more resources + +## ๐Ÿ“ Project Structure + +``` +sagemaker-code-editor/ +โ”œโ”€โ”€ .github/workflows/ +โ”‚ โ”œโ”€โ”€ ci.yml # Unit tests & validation +โ”‚ โ”œโ”€โ”€ build.yml # VS Code build with caching +โ”‚ โ””โ”€โ”€ e2e.yml # Integration tests +โ”œโ”€โ”€ scripts/ +โ”‚ โ”œโ”€โ”€ test-local.sh # Local development script +โ”‚ โ””โ”€โ”€ clean-cache.sh # Cache cleanup script +โ”œโ”€โ”€ patches/ # VS Code patches +โ”œโ”€โ”€ vscode/ # VS Code submodule +โ””โ”€โ”€ .local-cache/ # Local build cache (gitignored) +``` + +## ๐Ÿ”„ CI/CD Pipeline + +### ci.yml - Unit Tests +- **Triggers**: Every push/PR +- **Duration**: ~30 seconds +- **Purpose**: Fast feedback on code quality +- **Tests**: CSP validation, linting, security audit + +### build.yml - Build Process +- **Triggers**: Every push/PR +- **Duration**: 30-40 minutes +- **Purpose**: Create deployable artifacts +- **Features**: Smart caching, Node 22, memory optimization + +### e2e.yml - Integration Tests +- **Triggers**: After successful builds +- **Duration**: Variable +- **Purpose**: End-to-end validation +- **Dependencies**: Requires build artifacts + +## ๐Ÿ› Troubleshooting + +### Local Build Issues +```bash +# Clean everything and rebuild +./scripts/clean-cache.sh +./scripts/test-local.sh +``` + +### Memory Issues with act +```bash +# Increase Docker memory +~/bin/act -W .github/workflows/build.yml --container-options="--memory=8g --memory-swap=8g" + +# Or skip memory-intensive steps +~/bin/act -W .github/workflows/build.yml --skip-steps="Build vscode" +``` + +### Patch Application Failures +```bash +# Check patch status +cd vscode && quilt series -v + +# Reset patches +quilt pop -a +quilt push -a +``` + +### Cache Issues +```bash +# Check cache contents +ls -la .local-cache/ + +# Selective cleanup +rm -rf .local-cache/vscode-* +``` + +## ๐Ÿ’ก Tips + +### Performance +- Use cached builds for development (30 seconds vs 20 minutes) +- Only clean cache when necessary +- Test CI locally before pushing + +### Memory Management +- Local builds use 6GB heap limit +- GitHub Actions use 8GB heap limit +- Docker containers may need memory limits increased + +### Development Speed +1. **Fastest**: `~/bin/act -W .github/workflows/ci.yml` (30 seconds) +2. **Medium**: `./scripts/test-local.sh` (30 seconds cached, 20 minutes first time) +3. **Slowest**: Push to GitHub (30-40 minutes full pipeline) + +### Best Practices +- Test CI locally before pushing +- Use local development for iterative changes +- Clean cache only when needed +- Monitor memory usage during builds + +## ๐Ÿ”— Related Links +- [GitHub Actions Documentation](https://docs.github.com/en/actions) +- [act Documentation](https://github.com/nektos/act) +- [VS Code Build Documentation](https://github.com/microsoft/vscode/wiki/How-to-Contribute) \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..53080c0f8 --- /dev/null +++ b/Makefile @@ -0,0 +1,75 @@ +.PHONY: build-cache build install-act run-github run-local clean-vscode clean + +build-cache: + @echo "Building SageMaker Code Editor (multi-stage npm cache)..." + docker buildx build \ + --platform linux/amd64 \ + --progress=plain \ + --memory=32g \ + -t npm-cache:latest \ + -f scripts/Dockerfile.build.cache . + +build: + @echo "Building SageMaker Code Editor (original)..." + docker buildx build \ + --platform linux/amd64 \ + --progress=plain \ + --memory=32g \ + --output type=local,dest=./.artifacts \ + -t localbuild:latest \ + -f scripts/Dockerfile.build . + +install-act: + @echo "Installing act (GitHub Actions runner)..." + @if ! command -v act >/dev/null 2>&1 && [ ! -f ./bin/act ]; then \ + curl -s https://raw.githubusercontent.com/nektos/act/master/install.sh | bash; \ + echo "act installed successfully"; \ + else \ + echo "act is already available"; \ + fi + +run-github: install-act + @echo "Running complete GitHub Actions workflow locally..." + @echo "Available workflows:" + @ls -la .github/workflows/ + @echo "" + @echo "Running full build.yml workflow..." + @if command -v act >/dev/null 2>&1; then \ + act push -W .github/workflows/build.yml --platform ubuntu-22.04=catthehacker/ubuntu:act-22.04 --container-options "--memory=32g --memory-swap=32g"; \ + else \ + ./bin/act push -W .github/workflows/build.yml --platform ubuntu-22.04=catthehacker/ubuntu:act-22.04 --container-options "--memory=32g --memory-swap=32g"; \ + fi + +run-local: + @if [ -z "$(TARBALL)" ]; then \ + echo "Building and running SageMaker Code Editor locally on port 8888..."; \ + docker build -f scripts/Dockerfile.dev -t local-code-editor-dev . || exit 1; \ + echo "Stopping container..."; \ + docker stop local-code-editor-dev; \ + sleep 2; \ + echo "Starting container on http://localhost:8888"; \ + docker run --rm -d -p 8888:8000 -v .:/workspace --entrypoint /workspace/scripts/run-code-editor-dev.sh --name local-code-editor-dev local-code-editor-dev || exit 1; \ + docker logs -f local-code-editor-dev; \ + else \ + echo "Building and running SageMaker Code Editor locally on port 8888..."; \ + docker build -f scripts/Dockerfile.run --build-arg TARBALL=$(TARBALL) -t local-code-editor . || exit 1; \ + echo "Stopping container..."; \ + docker stop local-code-editor; \ + sleep 2; \ + echo "Starting container on http://localhost:8888"; \ + docker run --rm -d -p 8888:8000 --name local-code-editor local-code-editor || exit 1; \ + docker logs -f local-code-editor; \ + fi + +clean-vscode: + @echo "Cleaning VSCode node_modules..." + @find . -type d -name "node_modules" -exec rm -rf {} + 2>/dev/null || true + @rm -rf vscode/out/* 2>/dev/null || true + @echo "VSCode cleanup completed" + +clean: clean-vscode + @echo "Cleaning act temporary files and Docker images..." + @echo "Removing act cache..." + @rm -rf ~/.cache/act 2>/dev/null || true + @echo "Act cleanup completed" + diff --git a/README.md b/README.md index cc5b8da21..c3742b3ff 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,25 @@ This script will: - Run `yarn watch` from within the `vscode` folder - Open a new terminal and run `./vscode/scripts/code-server.sh --launch` +## Make Commands + +Available make targets for building and testing: + +### 1. When making local changes to iterate faster where tarball generation is not required [each run takes 10-20 mins] +- `make run-local` - Build and run SageMaker Code Editor locally from source and does not require a TARBALL; this process runs a watcher so changes are automatically picked from local workspace +- `make clean-vscode` - Cleans node_modules and out files + +### 2. Once local changes are tested; follow this process to generate minified tarball [each run takes ~40 mins to build] +- `make build-cache` - Build SageMaker Code Editor with multi-stage npm cache; Run once and layer gets cached with node_modules +- `make build` - Build SageMaker Code Editor and output artifacts (tarball) to ./artifacts +- `make run-local TARBALL=` - Build and run SageMaker Code Editor locally on port 8888 using specified tarball from previos step. Example: `make run-local TARBALL=sagemaker-code-editor-1.101.2.tar.gz` + +### 3. This process is used to test and simulate github workflows locally [each run takes ~60 mins] +- `make run-github` - Run complete GitHub Actions workflow locally using act + +### 4. Cleanup +- `make clean` - Cleans node_modules, out files, and act temporary files + ## Troubleshooting and Feedback For any issues that customers would like to report, please route to the `amazon-sagemaker-feedback` repository: https://github.com/aws/amazon-sagemaker-feedback diff --git a/patched-vscode/.vscode-test.js b/patched-vscode/.vscode-test.js index f2db8d695..b6a0b83e9 100644 --- a/patched-vscode/.vscode-test.js +++ b/patched-vscode/.vscode-test.js @@ -45,6 +45,11 @@ const extensions = [ label: 'github-authentication', workspaceFolder: path.join(os.tmpdir(), `msft-auth-${Math.floor(Math.random() * 100000)}`), mocha: { timeout: 60_000 } + }, + { + label: 'sagemaker-ui-dark-theme', + workspaceFolder: `extensions/sagemaker-ui-dark-theme/test-workspace`, + mocha: { timeout: 60_000 } } ]; diff --git a/patched-vscode/build/gulpfile.extensions.js b/patched-vscode/build/gulpfile.extensions.js index 1b8bd9b3d..9296656c9 100644 --- a/patched-vscode/build/gulpfile.extensions.js +++ b/patched-vscode/build/gulpfile.extensions.js @@ -62,7 +62,11 @@ const compilations = [ 'extensions/simple-browser/tsconfig.json', 'extensions/sagemaker-extension/tsconfig.json', 'extensions/sagemaker-idle-extension/tsconfig.json', + 'extensions/sagemaker-extensions-sync/tsconfig.json', 'extensions/sagemaker-terminal-crash-mitigation/tsconfig.json', + 'extensions/sagemaker-open-notebook-extension/tsconfig.json', + 'extensions/sagemaker-ui-dark-theme/tsconfig.json', + 'extensions/post-startup-notifications/tsconfig.json', 'extensions/tunnel-forwarding/tsconfig.json', 'extensions/typescript-language-features/test-workspace/tsconfig.json', 'extensions/typescript-language-features/web/tsconfig.json', diff --git a/patched-vscode/build/npm/dirs.js b/patched-vscode/build/npm/dirs.js index 2aa1c43f8..ec8679a59 100644 --- a/patched-vscode/build/npm/dirs.js +++ b/patched-vscode/build/npm/dirs.js @@ -40,8 +40,12 @@ const dirs = [ 'extensions/php-language-features', 'extensions/references-view', 'extensions/sagemaker-extension', + 'extensions/sagemaker-extensions-sync', 'extensions/sagemaker-idle-extension', 'extensions/sagemaker-terminal-crash-mitigation', + 'extensions/sagemaker-open-notebook-extension', + 'extensions/sagemaker-ui-dark-theme', + 'extensions/post-startup-notifications', 'extensions/search-result', 'extensions/simple-browser', 'extensions/tunnel-forwarding', diff --git a/patched-vscode/build/yarn.lock b/patched-vscode/build/yarn.lock index 3131c4321..a974ea01d 100644 --- a/patched-vscode/build/yarn.lock +++ b/patched-vscode/build/yarn.lock @@ -879,11 +879,11 @@ brace-expansion@^1.1.7: concat-map "0.0.1" braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" buffer-alloc-unsafe@^1.1.0: version "1.1.0" @@ -1422,10 +1422,10 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +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== dependencies: to-regex-range "^5.0.1" @@ -2567,9 +2567,9 @@ supports-color@^7.1.0: has-flag "^4.0.0" tar-fs@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" - integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== + version "2.1.3" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.3.tgz#fb3b8843a26b6f13a08e606f7922875eb1fbbf92" + integrity sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg== dependencies: chownr "^1.1.1" mkdirp-classic "^0.5.2" diff --git a/patched-vscode/extensions/npm/yarn.lock b/patched-vscode/extensions/npm/yarn.lock index a7afc9f80..d1f79c9f3 100644 --- a/patched-vscode/extensions/npm/yarn.lock +++ b/patched-vscode/extensions/npm/yarn.lock @@ -38,22 +38,22 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -braces@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== +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== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +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== dependencies: to-regex-range "^5.0.1" @@ -118,12 +118,12 @@ locate-path@^6.0.0: p-locate "^5.0.0" micromatch@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" - integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.1" - picomatch "^2.0.5" + braces "^3.0.3" + picomatch "^2.3.1" minimatch@^5.1.6: version "5.1.6" @@ -151,10 +151,10 @@ path-exists@^4.0.0: resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== -picomatch@^2.0.5: - version "2.2.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" - integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== pify@^4.0.1: version "4.0.1" diff --git a/patched-vscode/extensions/post-startup-notifications/.vscode/extensions.json b/patched-vscode/extensions/post-startup-notifications/.vscode/extensions.json new file mode 100644 index 000000000..dd01eb355 --- /dev/null +++ b/patched-vscode/extensions/post-startup-notifications/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": ["dbaeumer.vscode-eslint", "amodio.tsl-problem-matcher", "ms-vscode.extension-test-runner"] +} diff --git a/patched-vscode/extensions/post-startup-notifications/.vscode/launch.json b/patched-vscode/extensions/post-startup-notifications/.vscode/launch.json new file mode 100644 index 000000000..c33cd6513 --- /dev/null +++ b/patched-vscode/extensions/post-startup-notifications/.vscode/launch.json @@ -0,0 +1,21 @@ +// A launch configuration that compiles the extension and then opens it inside a new window +// Use IntelliSense to learn about possible attributes. +// Hover to view descriptions of existing attributes. +// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}" + ], + "outFiles": [ + "${workspaceFolder}/dist/**/*.js" + ], + "preLaunchTask": "${defaultBuildTask}" + } + ] +} \ No newline at end of file diff --git a/patched-vscode/extensions/post-startup-notifications/.vscode/settings.json b/patched-vscode/extensions/post-startup-notifications/.vscode/settings.json new file mode 100644 index 000000000..5c5ac48c5 --- /dev/null +++ b/patched-vscode/extensions/post-startup-notifications/.vscode/settings.json @@ -0,0 +1,13 @@ +// Place your settings in this file to overwrite default and user settings. +{ + "files.exclude": { + "out": false, // set this to true to hide the "out" folder with the compiled JS files + "dist": false // set this to true to hide the "dist" folder with the compiled JS files + }, + "search.exclude": { + "out": true, // set this to false to include "out" folder in search results + "dist": true // set this to false to include "dist" folder in search results + }, + // Turn off tsc task auto detection since we have the necessary tasks as npm scripts + "typescript.tsc.autoDetect": "off" +} \ No newline at end of file diff --git a/patched-vscode/extensions/post-startup-notifications/.vscode/tasks.json b/patched-vscode/extensions/post-startup-notifications/.vscode/tasks.json new file mode 100644 index 000000000..03254a468 --- /dev/null +++ b/patched-vscode/extensions/post-startup-notifications/.vscode/tasks.json @@ -0,0 +1,40 @@ +// See https://go.microsoft.com/fwlink/?LinkId=733558 +// for the documentation about the tasks.json format +{ + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "watch", + "problemMatcher": "$ts-webpack-watch", + "isBackground": true, + "presentation": { + "reveal": "never", + "group": "watchers" + }, + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "type": "npm", + "script": "watch-tests", + "problemMatcher": "$tsc-watch", + "isBackground": true, + "presentation": { + "reveal": "never", + "group": "watchers" + }, + "group": "build" + }, + { + "label": "tasks: watch-tests", + "dependsOn": [ + "npm: watch", + "npm: watch-tests" + ], + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/patched-vscode/extensions/post-startup-notifications/.vscodeignore b/patched-vscode/extensions/post-startup-notifications/.vscodeignore new file mode 100644 index 000000000..d255964ea --- /dev/null +++ b/patched-vscode/extensions/post-startup-notifications/.vscodeignore @@ -0,0 +1,14 @@ +.vscode/** +.vscode-test/** +out/** +node_modules/** +src/** +.gitignore +.yarnrc +webpack.config.js +vsc-extension-quickstart.md +**/tsconfig.json +**/eslint.config.mjs +**/*.map +**/*.ts +**/.vscode-test.* diff --git a/patched-vscode/extensions/post-startup-notifications/CHANGELOG.md b/patched-vscode/extensions/post-startup-notifications/CHANGELOG.md new file mode 100644 index 000000000..0f374b059 --- /dev/null +++ b/patched-vscode/extensions/post-startup-notifications/CHANGELOG.md @@ -0,0 +1,9 @@ +# Change Log + +All notable changes to the "post-startup-notifications" extension will be documented in this file. + +Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. + +## [Unreleased] + +- Initial release \ No newline at end of file diff --git a/patched-vscode/extensions/post-startup-notifications/README.md b/patched-vscode/extensions/post-startup-notifications/README.md new file mode 100644 index 000000000..4f1efff5c --- /dev/null +++ b/patched-vscode/extensions/post-startup-notifications/README.md @@ -0,0 +1,4 @@ +# post-startup-notifications README + +This extension monitors post startup script execution and notifies users on success/failure. + diff --git a/patched-vscode/extensions/post-startup-notifications/__mocks__/vscode.js b/patched-vscode/extensions/post-startup-notifications/__mocks__/vscode.js new file mode 100644 index 000000000..e69de29bb diff --git a/patched-vscode/extensions/post-startup-notifications/eslint.config.mjs b/patched-vscode/extensions/post-startup-notifications/eslint.config.mjs new file mode 100644 index 000000000..d5c0b53a7 --- /dev/null +++ b/patched-vscode/extensions/post-startup-notifications/eslint.config.mjs @@ -0,0 +1,28 @@ +import typescriptEslint from "@typescript-eslint/eslint-plugin"; +import tsParser from "@typescript-eslint/parser"; + +export default [{ + files: ["**/*.ts"], +}, { + plugins: { + "@typescript-eslint": typescriptEslint, + }, + + languageOptions: { + parser: tsParser, + ecmaVersion: 2022, + sourceType: "module", + }, + + rules: { + "@typescript-eslint/naming-convention": ["warn", { + selector: "import", + format: ["camelCase", "PascalCase"], + }], + + curly: "warn", + eqeqeq: "warn", + "no-throw-literal": "warn", + semi: "warn", + }, +}]; \ No newline at end of file diff --git a/patched-vscode/extensions/post-startup-notifications/extension-browser.webpack.config.js b/patched-vscode/extensions/post-startup-notifications/extension-browser.webpack.config.js new file mode 100644 index 000000000..68271e0e9 --- /dev/null +++ b/patched-vscode/extensions/post-startup-notifications/extension-browser.webpack.config.js @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright Amazon.com Inc. or its affiliates. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check + +'use strict'; + +const withBrowserDefaults = require('../shared.webpack.config').browser; + +module.exports = withBrowserDefaults({ + context: __dirname, + entry: { + extension: './src/extension.ts' + }, +}); diff --git a/patched-vscode/extensions/post-startup-notifications/extension.webpack.config.js b/patched-vscode/extensions/post-startup-notifications/extension.webpack.config.js new file mode 100644 index 000000000..61c91aab5 --- /dev/null +++ b/patched-vscode/extensions/post-startup-notifications/extension.webpack.config.js @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright Amazon.com Inc. or its affiliates. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check + +'use strict'; + +const withDefaults = require('../shared.webpack.config'); + +module.exports = withDefaults({ + context: __dirname, + resolve: { + mainFields: ['module', 'main'] + }, + entry: { + extension: './src/extension.ts', + } +}); \ No newline at end of file diff --git a/patched-vscode/extensions/post-startup-notifications/package.json b/patched-vscode/extensions/post-startup-notifications/package.json new file mode 100644 index 000000000..3e3ab3117 --- /dev/null +++ b/patched-vscode/extensions/post-startup-notifications/package.json @@ -0,0 +1,57 @@ +{ + "name": "post-startup-notifications", + "displayName": "post-startup-notifications", + "description": "Extension for surfacing post startup script status notifications to users", + "version": "0.0.1", + "publisher": "sagemaker", + "license": "MIT", + "engines": { + "vscode": "^1.98.0" + }, + "categories": [ + "Other" + ], + "activationEvents": [ + "*" + ], + "main": "./dist/extension.js", + "contributes": { + "commands": [] + }, + "scripts": { + "test": "jest", + "compile": "gulp compile-extension:post-startup-notifications", + "watch": "npm run build-preview && gulp watch-extension:post-startup-notifications", + "vscode:prepublish": "npm run build-ext", + "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:post-startup-notifications ./tsconfig.json" + }, + "jest": { + "preset": "ts-jest", + "testEnvironment": "node", + "moduleFileExtensions": [ + "ts", + "js" + ] + }, + "devDependencies": { + "@types/jest": "^29.5.14", + "@types/mocha": "^10.0.10", + "@types/node": "20.x", + "@types/vscode": "^1.98.0", + "@typescript-eslint/eslint-plugin": "^8.25.0", + "@typescript-eslint/parser": "^8.25.0", + "@vscode/test-cli": "^0.0.10", + "@vscode/test-electron": "^2.4.1", + "eslint": "^9.21.0", + "jest": "^29.7.0", + "mocha": "^11.1.0", + "ts-jest": "^29.3.0", + "ts-loader": "^9.5.2", + "typescript": "^5.7.3", + "webpack": "^5.98.0", + "webpack-cli": "^6.0.1" + }, + "dependencies": { + "chokidar": "^4.0.3" + } +} \ No newline at end of file diff --git a/patched-vscode/extensions/post-startup-notifications/src/constant.ts b/patched-vscode/extensions/post-startup-notifications/src/constant.ts new file mode 100644 index 000000000..8dfbf8882 --- /dev/null +++ b/patched-vscode/extensions/post-startup-notifications/src/constant.ts @@ -0,0 +1,3 @@ +export const POST_START_UP_STATUS_FILE = '/tmp/.post-startup-status.json'; +export const SERVICE_NAME_ENV_VALUE = 'SageMakerUnifiedStudio'; +export const SERVICE_NAME_ENV_KEY = 'SERVICE_NAME'; \ No newline at end of file diff --git a/patched-vscode/extensions/post-startup-notifications/src/extension.ts b/patched-vscode/extensions/post-startup-notifications/src/extension.ts new file mode 100644 index 000000000..39fb65aaa --- /dev/null +++ b/patched-vscode/extensions/post-startup-notifications/src/extension.ts @@ -0,0 +1,117 @@ +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import { POST_START_UP_STATUS_FILE, SERVICE_NAME_ENV_KEY, SERVICE_NAME_ENV_VALUE } from './constant'; +import { StatusFile } from './types'; +import * as chokidar from 'chokidar'; + +// Simple method to check if user has seen a notification +function hasUserSeen(context: vscode.ExtensionContext, notificationId: string): boolean { + return context.globalState.get(`notification_seen_${notificationId}`) === true; +} + +// Simple method to mark notification as seen +function markAsSeen(context: vscode.ExtensionContext, notificationId: string): void { + context.globalState.update(`notification_seen_${notificationId}`, true); +} + +let previousStatus: string | undefined; +let watcher: chokidar.FSWatcher; +let outputChannel: vscode.OutputChannel; + +export function activate(context: vscode.ExtensionContext) { + // Check if in SageMaker Unified Studio + const envValue = process.env[SERVICE_NAME_ENV_KEY]; + + if (!envValue || envValue !== SERVICE_NAME_ENV_VALUE) { + return; + } + + outputChannel = vscode.window.createOutputChannel('SageMaker Unified Studio Post Startup Notifications'); + + // Show Q CLI notification if user hasn't seen it before + showQCliNotification(context); + + try { + watcher = chokidar.watch(POST_START_UP_STATUS_FILE, { + persistent: true, + ignoreInitial: false, + awaitWriteFinish: { + stabilityThreshold: 2000, + pollInterval: 100 + } + }); + + watcher.on('add', (path) => { + processStatusFile(); + }).on('change', (path) => { + processStatusFile(); + }).on('unlink', (path) => { + outputChannel.appendLine(`File ${path} has been removed`); + }); + + } catch (error: any) { + outputChannel.appendLine(`Error setting up file watcher: ${error}`); + } +} + +function processStatusFile() { + try { + const content = fs.readFileSync(POST_START_UP_STATUS_FILE, 'utf8'); + const statusData: StatusFile = JSON.parse(content); + + // Only show message if status has changed + if (statusData.status && statusData.status !== previousStatus) { + previousStatus = statusData.status; + + if (statusData.message) { + switch (statusData.status.toLowerCase()) { + case 'error': + vscode.window.showErrorMessage(statusData.message); + break; + case 'in-progress': + default: + vscode.window.showInformationMessage(statusData.message); + } + } + } + } catch (error: any) { + if (error.code !== 'ENOENT') { + outputChannel.appendLine(`Error processing status file: ${error.message}`); + } + } +}; + +// Show Q CLI notification if user hasn't seen it before +function showQCliNotification(context: vscode.ExtensionContext): void { + const notificationId = 'smus_q_cli_notification'; + const message = 'The Amazon Q Command Line Interface (CLI) is installed. You can now access AI-powered assistance in your terminal.'; + const link = 'https://docs.aws.amazon.com/sagemaker-unified-studio/latest/userguide/q-actions.html'; + const linkLabel = 'Learn More'; + + if (!hasUserSeen(context, notificationId)) { + outputChannel.appendLine("User has not seen the notification") + // Show notification with Learn More button + vscode.window.showInformationMessage( + message, + { modal: false }, + { title: linkLabel, isCloseAffordance: false } + ).then((selection) => { + if (selection && selection.title === linkLabel) { + vscode.env.openExternal(vscode.Uri.parse(link)); + } + + // Mark as seen regardless of which button was clicked + markAsSeen(context, notificationId); + }); + } +} + +export function deactivate() { + if (watcher) { + watcher.close(); + } + outputChannel.appendLine('Status monitor deactivated'); + if (outputChannel) { + outputChannel.dispose(); + } +} \ No newline at end of file diff --git a/patched-vscode/extensions/post-startup-notifications/src/test/extension.test.ts b/patched-vscode/extensions/post-startup-notifications/src/test/extension.test.ts new file mode 100644 index 000000000..950d6903f --- /dev/null +++ b/patched-vscode/extensions/post-startup-notifications/src/test/extension.test.ts @@ -0,0 +1,267 @@ +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import * as chokidar from 'chokidar'; +import { activate, deactivate } from '../extension'; +import { POST_START_UP_STATUS_FILE, SERVICE_NAME_ENV_KEY, SERVICE_NAME_ENV_VALUE } from '../constant'; + +type MockCall = [string, (path: string) => void]; + +interface MockFSWatcher extends chokidar.FSWatcher { + on: jest.Mock; + close: jest.Mock; +} + +// Mocks setup +jest.mock('vscode', () => ({ + window: { + showErrorMessage: jest.fn(), + showInformationMessage: jest.fn().mockReturnValue(Promise.resolve()), + createOutputChannel: jest.fn() + }, + env: { + openExternal: jest.fn() + }, + Uri: { + parse: jest.fn(url => ({ toString: () => url })) + } +})); + +jest.mock('fs'); +jest.mock('chokidar'); + +describe('SageMaker Unified Studio Extension Tests', () => { + let mockContext: vscode.ExtensionContext; + let mockWatcher: MockFSWatcher; + let mockOutputChannel: vscode.OutputChannel; + + beforeEach(() => { + // Reset mocks + jest.resetAllMocks(); + + // Setup context with globalState for storage + mockContext = { + subscriptions: [], + globalState: { + get: jest.fn(), + update: jest.fn(), + keys: jest.fn().mockReturnValue([]) + } + } as any; + + // Setup watcher + mockWatcher = { + on: jest.fn().mockReturnThis(), + close: jest.fn() + } as any; + + mockOutputChannel = { + appendLine: jest.fn(), + dispose: jest.fn() + } as any; + + (chokidar.watch as jest.Mock).mockReturnValue(mockWatcher); + (vscode.window.createOutputChannel as jest.Mock).mockReturnValue(mockOutputChannel); + process.env[SERVICE_NAME_ENV_KEY] = SERVICE_NAME_ENV_VALUE; + }); + + // Helper function to get watcher callbacks + const getWatcherCallback = (eventType: string): ((path: string) => void) => { + const call = mockWatcher.on.mock.calls.find( + (call: MockCall) => call[0] === eventType + ); + return call ? call[1] : jest.fn(); + }; + + // Helper function to simulate file content + const simulateFileContent = (content: object): void => { + (fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(content)); + }; + + describe('Activation Tests', () => { + test('should not activate outside SageMaker environment', () => { + process.env[SERVICE_NAME_ENV_KEY] = 'wrong-value'; + activate(mockContext); + expect(vscode.window.createOutputChannel).not.toHaveBeenCalled(); + }); + + test('should initialize properly in SageMaker environment', () => { + activate(mockContext); + expect(vscode.window.createOutputChannel).toHaveBeenCalledWith( + 'SageMaker Unified Studio Post Startup Notifications' + ); + expect(chokidar.watch).toHaveBeenCalledWith( + POST_START_UP_STATUS_FILE, + expect.objectContaining({ + persistent: true, + ignoreInitial: false + }) + ); + }); + + test('should handle watcher setup errors', () => { + const error = new Error('Setup error'); + (chokidar.watch as jest.Mock).mockImplementation(() => { throw error; }); + activate(mockContext); + expect(mockOutputChannel.appendLine).toHaveBeenCalled(); + }); + }); + + describe('File Processing Tests', () => { + test('should handle error status', () => { + simulateFileContent({ + status: 'error', + message: 'Test error message' + }); + + activate(mockContext); + getWatcherCallback('add')('test-path'); + + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith('Test error message'); + }); + + test('should handle in-progress status', () => { + simulateFileContent({ + status: 'in-progress', + message: 'Processing message' + }); + + activate(mockContext); + getWatcherCallback('add')('test-path'); + + expect(vscode.window.showInformationMessage).toHaveBeenCalledWith('Processing message'); + }); + + test('should not show message for unchanged status', () => { + simulateFileContent({ + status: 'error', + message: 'Error message' + }); + + activate(mockContext); + const addCallback = getWatcherCallback('add'); + addCallback('test-path'); + expect(vscode.window.showErrorMessage).toHaveBeenCalledTimes(1); + + addCallback('test-path'); + expect(vscode.window.showErrorMessage).toHaveBeenCalledTimes(1); + }); + + test('should handle file removal', () => { + activate(mockContext); + getWatcherCallback('unlink')('test-path'); + expect(mockOutputChannel.appendLine).toHaveBeenCalledWith('File test-path has been removed'); + }); + }); + + describe('Error Handling Tests', () => { + test('should handle invalid JSON', () => { + (fs.readFileSync as jest.Mock).mockReturnValue('invalid json'); + + activate(mockContext); + getWatcherCallback('add')('test-path'); + + expect(mockOutputChannel.appendLine).toHaveBeenCalledWith( + expect.stringContaining('Error processing status file') + ); + }); + + test('should handle file read errors', () => { + (fs.readFileSync as jest.Mock).mockImplementation(() => { + throw new Error('Read error'); + }); + + activate(mockContext); + getWatcherCallback('add')('test-path'); + + expect(mockOutputChannel.appendLine).toHaveBeenCalledWith( + expect.stringContaining('Error processing status file') + ); + }); + + test('should ignore ENOENT errors', () => { + const error = new Error('File not found'); + (error as any).code = 'ENOENT'; + (fs.readFileSync as jest.Mock).mockImplementation(() => { + throw error; + }); + + activate(mockContext); + getWatcherCallback('add')('test-path'); + + expect(mockOutputChannel.appendLine).not.toHaveBeenCalled(); + }); + + test('should handle missing status or message', () => { + simulateFileContent({}); + + activate(mockContext); + getWatcherCallback('add')('test-path'); + + expect(vscode.window.showErrorMessage).not.toHaveBeenCalled(); + expect(vscode.window.showInformationMessage).not.toHaveBeenCalled(); + }); + }); + + describe('Q CLI Notification Tests', () => { + test('should show Q CLI notification with Learn More button', () => { + // Set up globalState to simulate first-time user + (mockContext.globalState.get as jest.Mock).mockReturnValue(undefined); + + activate(mockContext); + + // Verify notification is shown with correct message and button + expect(vscode.window.showInformationMessage).toHaveBeenCalledWith( + 'The Amazon Q Command Line Interface (CLI) is installed. You can now access AI-powered assistance in your terminal.', + { modal: false }, + { title: 'Learn More', isCloseAffordance: false } + ); + }); + + test('should open documentation when Learn More is clicked', async () => { + // Set up globalState to simulate first-time user + (mockContext.globalState.get as jest.Mock).mockReturnValue(undefined); + + // Mock the user clicking "Learn More" + const mockSelection = { title: 'Learn More' }; + (vscode.window.showInformationMessage as jest.Mock).mockReturnValue(Promise.resolve(mockSelection)); + + activate(mockContext); + + // Wait for the promise to resolve + await new Promise(process.nextTick); + + // Verify the documentation link is opened + expect(vscode.env.openExternal).toHaveBeenCalledWith( + expect.objectContaining({ + toString: expect.any(Function) + }) + ); + + // Verify notification is marked as seen + expect(mockContext.globalState.update).toHaveBeenCalledWith( + 'notification_seen_smus_q_cli_notification', + true + ); + }); + + test('should not show notification if already seen', () => { + // Set up globalState to simulate returning user who has seen notification + (mockContext.globalState.get as jest.Mock).mockReturnValue(true); + + activate(mockContext); + + // Verify notification is not shown again + expect(vscode.window.showInformationMessage).not.toHaveBeenCalled(); + }); + }); + + describe('Deactivation Tests', () => { + test('should cleanup resources properly', () => { + activate(mockContext); + deactivate(); + + expect(mockWatcher.close).toHaveBeenCalled(); + expect(mockOutputChannel.dispose).toHaveBeenCalled(); + }); + }); +}); \ No newline at end of file diff --git a/patched-vscode/extensions/post-startup-notifications/src/types.ts b/patched-vscode/extensions/post-startup-notifications/src/types.ts new file mode 100644 index 000000000..ec213205d --- /dev/null +++ b/patched-vscode/extensions/post-startup-notifications/src/types.ts @@ -0,0 +1,6 @@ +export interface StatusFile { + status: string; + message: string; + link: string; + label: string; +} \ No newline at end of file diff --git a/patched-vscode/extensions/post-startup-notifications/tsconfig.json b/patched-vscode/extensions/post-startup-notifications/tsconfig.json new file mode 100644 index 000000000..1fd1f7666 --- /dev/null +++ b/patched-vscode/extensions/post-startup-notifications/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "module": "Node16", + "target": "ES2022", + "lib": [ + "ES2022" + ], + "sourceMap": true, + "rootDir": "src", + "strict": true, /* enable all strict type-checking options */ + "isolatedModules": true + /* Additional Checks */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + } +} \ No newline at end of file diff --git a/patched-vscode/extensions/post-startup-notifications/webpack.config.js b/patched-vscode/extensions/post-startup-notifications/webpack.config.js new file mode 100644 index 000000000..a57c31cf8 --- /dev/null +++ b/patched-vscode/extensions/post-startup-notifications/webpack.config.js @@ -0,0 +1,48 @@ +//@ts-check + +'use strict'; + +const path = require('path'); + +//@ts-check +/** @typedef {import('webpack').Configuration} WebpackConfig **/ + +/** @type WebpackConfig */ +const extensionConfig = { + target: 'node', // VS Code extensions run in a Node.js-context ๐Ÿ“– -> https://webpack.js.org/configuration/node/ + mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production') + + entry: './src/extension.ts', // the entry point of this extension, ๐Ÿ“– -> https://webpack.js.org/configuration/entry-context/ + output: { + // the bundle is stored in the 'dist' folder (check package.json), ๐Ÿ“– -> https://webpack.js.org/configuration/output/ + path: path.resolve(__dirname, 'dist'), + filename: 'extension.js', + libraryTarget: 'commonjs2' + }, + externals: { + vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, ๐Ÿ“– -> https://webpack.js.org/configuration/externals/ + // modules added here also need to be added in the .vscodeignore file + }, + resolve: { + // support reading TypeScript and JavaScript files, ๐Ÿ“– -> https://github.com/TypeStrong/ts-loader + extensions: ['.ts', '.js'] + }, + module: { + rules: [ + { + test: /\.ts$/, + exclude: /node_modules/, + use: [ + { + loader: 'ts-loader' + } + ] + } + ] + }, + devtool: 'nosources-source-map', + infrastructureLogging: { + level: "log", // enables logging required for problem matchers + }, +}; +module.exports = [extensionConfig]; \ No newline at end of file diff --git a/patched-vscode/extensions/post-startup-notifications/yarn.lock b/patched-vscode/extensions/post-startup-notifications/yarn.lock new file mode 100644 index 000000000..c0e9e20e8 --- /dev/null +++ b/patched-vscode/extensions/post-startup-notifications/yarn.lock @@ -0,0 +1,3879 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.26.2": + 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/compat-data@^7.26.8": + version "7.26.8" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.8.tgz#821c1d35641c355284d4a870b8a4a7b0c141e367" + integrity sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.10.tgz#5c876f83c8c4dcb233ee4b670c0606f2ac3000f9" + integrity sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.26.2" + "@babel/generator" "^7.26.10" + "@babel/helper-compilation-targets" "^7.26.5" + "@babel/helper-module-transforms" "^7.26.0" + "@babel/helpers" "^7.26.10" + "@babel/parser" "^7.26.10" + "@babel/template" "^7.26.9" + "@babel/traverse" "^7.26.10" + "@babel/types" "^7.26.10" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.26.10", "@babel/generator@^7.27.0", "@babel/generator@^7.7.2": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.27.0.tgz#764382b5392e5b9aff93cadb190d0745866cbc2c" + integrity sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw== + dependencies: + "@babel/parser" "^7.27.0" + "@babel/types" "^7.27.0" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^3.0.2" + +"@babel/helper-compilation-targets@^7.26.5": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz#de0c753b1cd1d9ab55d473c5a5cf7170f0a81880" + integrity sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA== + dependencies: + "@babel/compat-data" "^7.26.8" + "@babel/helper-validator-option" "^7.25.9" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-module-imports@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715" + integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw== + dependencies: + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + +"@babel/helper-module-transforms@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" + integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== + dependencies: + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@babel/traverse" "^7.25.9" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.25.9", "@babel/helper-plugin-utils@^7.8.0": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz#18580d00c9934117ad719392c4f6585c9333cc35" + integrity sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg== + +"@babel/helper-string-parser@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" + integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== + +"@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== + +"@babel/helper-validator-option@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72" + integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw== + +"@babel/helpers@^7.26.10": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.27.0.tgz#53d156098defa8243eab0f32fa17589075a1b808" + integrity sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg== + dependencies: + "@babel/template" "^7.27.0" + "@babel/types" "^7.27.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.26.10", "@babel/parser@^7.27.0": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.0.tgz#3d7d6ee268e41d2600091cbd4e145ffee85a44ec" + integrity sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg== + dependencies: + "@babel/types" "^7.27.0" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-import-attributes@^7.24.7": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz#3b1412847699eea739b4f2602c74ce36f6b0b0f7" + integrity sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz#a34313a178ea56f1951599b929c1ceacee719290" + integrity sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz#67dda2b74da43727cf21d46cf9afef23f4365399" + integrity sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/template@^7.26.9", "@babel/template@^7.27.0", "@babel/template@^7.3.3": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.0.tgz#b253e5406cc1df1c57dcd18f11760c2dbf40c0b4" + integrity sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA== + dependencies: + "@babel/code-frame" "^7.26.2" + "@babel/parser" "^7.27.0" + "@babel/types" "^7.27.0" + +"@babel/traverse@^7.25.9", "@babel/traverse@^7.26.10": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.27.0.tgz#11d7e644779e166c0442f9a07274d02cd91d4a70" + integrity sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA== + dependencies: + "@babel/code-frame" "^7.26.2" + "@babel/generator" "^7.27.0" + "@babel/parser" "^7.27.0" + "@babel/template" "^7.27.0" + "@babel/types" "^7.27.0" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.9", "@babel/types@^7.26.10", "@babel/types@^7.27.0", "@babel/types@^7.3.3": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.0.tgz#ef9acb6b06c3173f6632d993ecb6d4ae470b4559" + integrity sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg== + dependencies: + "@babel/helper-string-parser" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@discoveryjs/json-ext@^0.6.1": + version "0.6.3" + resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz#f13c7c205915eb91ae54c557f5e92bddd8be0e83" + integrity sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ== + +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.5.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz#b0fc7e06d0c94f801537fd4237edc2706d3b8e4c" + integrity sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.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-array@^0.19.2": + version "0.19.2" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.19.2.tgz#3060b809e111abfc97adb0bb1172778b90cb46aa" + integrity sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w== + dependencies: + "@eslint/object-schema" "^2.1.6" + debug "^4.3.1" + minimatch "^3.1.2" + +"@eslint/config-helpers@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.2.0.tgz#12dc8d65c31c4b6c3ebf0758db6601eb7692ce59" + integrity sha512-yJLLmLexii32mGrhW29qvU3QBVTu0GUmEf/J4XsBtVhp4JkIUFN/BjWqTF63yRvGApIDpZm5fa97LtYtINmfeQ== + +"@eslint/core@^0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.12.0.tgz#5f960c3d57728be9f6c65bd84aa6aa613078798e" + integrity sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg== + dependencies: + "@types/json-schema" "^7.0.15" + +"@eslint/eslintrc@^3.3.1": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.1.tgz#e55f7f1dd400600dd066dbba349c4c0bac916964" + integrity sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^10.0.1" + globals "^14.0.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@9.23.0": + version "9.23.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.23.0.tgz#c09ded4f3dc63b40b933bcaeb853fceddb64da30" + integrity sha512-35MJ8vCPU0ZMxo7zfev2pypqTwWTofFZO6m4KAtdoFhRpLJUpHTZZ+KB3C7Hb1d7bULYwO4lJXGCi5Se+8OMbw== + +"@eslint/object-schema@^2.1.6": + version "2.1.6" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f" + integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== + +"@eslint/plugin-kit@^0.2.7": + version "0.2.7" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz#9901d52c136fb8f375906a73dcc382646c3b6a27" + integrity sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g== + dependencies: + "@eslint/core" "^0.12.0" + levn "^0.4.1" + +"@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.6" + resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.6.tgz#ee2a10eaabd1131987bf0488fd9b820174cd765e" + integrity sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw== + dependencies: + "@humanfs/core" "^0.19.1" + "@humanwhocodes/retry" "^0.3.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/retry@^0.3.0": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a" + integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== + +"@humanwhocodes/retry@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.2.tgz#1860473de7dfa1546767448f333db80cb0ff2161" + integrity sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ== + +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== + dependencies: + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" + +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== + dependencies: + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== + dependencies: + "@jest/test-result" "^29.7.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + slash "^3.0.0" + +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.8" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" + integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + 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/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/source-map@^0.3.3": + version "0.3.6" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a" + integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@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": + 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== + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sinonjs/commons@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@types/babel__core@^7.1.14": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.8" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" + integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.7.tgz#968cdc2366ec3da159f61166428ee40f370e56c2" + integrity sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng== + dependencies: + "@babel/types" "^7.20.7" + +"@types/eslint-scope@^3.7.7": + version "3.7.7" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" + integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "9.6.1" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-9.6.1.tgz#d5795ad732ce81715f27f75da913004a56751584" + integrity sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*", "@types/estree@^1.0.6": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.7.tgz#4158d3105276773d5b7695cd4834b1722e4f37a8" + integrity sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ== + +"@types/graceful-fs@^4.1.3": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^29.5.14": + version "29.5.14" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5" + integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + +"@types/json-schema@*", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.9": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/mocha@^10.0.10", "@types/mocha@^10.0.2": + version "10.0.10" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.10.tgz#91f62905e8d23cbd66225312f239454a23bebfa0" + integrity sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q== + +"@types/node@*": + version "22.13.14" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.14.tgz#70d84ec91013dcd2ba2de35532a5a14c2b4cc912" + integrity sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w== + dependencies: + undici-types "~6.20.0" + +"@types/node@20.x": + version "20.17.28" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.17.28.tgz#c10436f3a3c996f535919a9b082e2c47f19c40a1" + integrity sha512-DHlH/fNL6Mho38jTy7/JT7sn2wnXI+wULR6PV4gy4VHLVvnrV/d3pHAMQHhc4gjdLmK2ZiPoMxzp6B3yRajLSQ== + dependencies: + undici-types "~6.19.2" + +"@types/stack-utils@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/vscode@^1.98.0": + version "1.98.0" + resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.98.0.tgz#5b6fa5bd99ba15313567d3847fb1177832fee08c" + integrity sha512-+KuiWhpbKBaG2egF+51KjbGWatTH5BbmWQjSLMDCssb4xF8FJnW4nGH4nuAdOOfMbpD0QlHtI+C3tPq+DoKElg== + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@^8.25.0": + version "8.29.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.0.tgz#151c4878700a5ad229ce6713d2674d58b626b3d9" + integrity sha512-PAIpk/U7NIS6H7TEtN45SPGLQaHNgB7wSjsQV/8+KYokAb2T/gloOA/Bee2yd4/yKVhPKe5LlaUGhAZk5zmSaQ== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "8.29.0" + "@typescript-eslint/type-utils" "8.29.0" + "@typescript-eslint/utils" "8.29.0" + "@typescript-eslint/visitor-keys" "8.29.0" + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^2.0.1" + +"@typescript-eslint/parser@^8.25.0": + version "8.29.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.29.0.tgz#b98841e0a8099728cb8583da92326fcb7f5be1d2" + integrity sha512-8C0+jlNJOwQso2GapCVWWfW/rzaq7Lbme+vGUFKE31djwNncIpgXD7Cd4weEsDdkoZDjH0lwwr3QDQFuyrMg9g== + dependencies: + "@typescript-eslint/scope-manager" "8.29.0" + "@typescript-eslint/types" "8.29.0" + "@typescript-eslint/typescript-estree" "8.29.0" + "@typescript-eslint/visitor-keys" "8.29.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@8.29.0": + version "8.29.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.29.0.tgz#8fd9872823aef65ff71d3f6d1ec9316ace0b6bf3" + integrity sha512-aO1PVsq7Gm+tcghabUpzEnVSFMCU4/nYIgC2GOatJcllvWfnhrgW0ZEbnTxm36QsikmCN1K/6ZgM7fok2I7xNw== + dependencies: + "@typescript-eslint/types" "8.29.0" + "@typescript-eslint/visitor-keys" "8.29.0" + +"@typescript-eslint/type-utils@8.29.0": + version "8.29.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.29.0.tgz#98dcfd1193cb4e2b2d0294a8656ce5eb58c443a9" + integrity sha512-ahaWQ42JAOx+NKEf5++WC/ua17q5l+j1GFrbbpVKzFL/tKVc0aYY8rVSYUpUvt2hUP1YBr7mwXzx+E/DfUWI9Q== + dependencies: + "@typescript-eslint/typescript-estree" "8.29.0" + "@typescript-eslint/utils" "8.29.0" + debug "^4.3.4" + ts-api-utils "^2.0.1" + +"@typescript-eslint/types@8.29.0": + version "8.29.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.29.0.tgz#65add70ab4ef66beaa42a5addf87dab2b05b1f33" + integrity sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg== + +"@typescript-eslint/typescript-estree@8.29.0": + version "8.29.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.0.tgz#d201a4f115327ec90496307c9958262285065b00" + integrity sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow== + dependencies: + "@typescript-eslint/types" "8.29.0" + "@typescript-eslint/visitor-keys" "8.29.0" + debug "^4.3.4" + fast-glob "^3.3.2" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^2.0.1" + +"@typescript-eslint/utils@8.29.0": + version "8.29.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.29.0.tgz#d6d22b19c8c4812a874f00341f686b45b9fe895f" + integrity sha512-gX/A0Mz9Bskm8avSWFcK0gP7cZpbY4AIo6B0hWYFCaIsz750oaiWR4Jr2CI+PQhfW1CpcQr9OlfPS+kMFegjXA== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "8.29.0" + "@typescript-eslint/types" "8.29.0" + "@typescript-eslint/typescript-estree" "8.29.0" + +"@typescript-eslint/visitor-keys@8.29.0": + version "8.29.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.0.tgz#2356336c9efdc3597ffcd2aa1ce95432852b743d" + integrity sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg== + dependencies: + "@typescript-eslint/types" "8.29.0" + eslint-visitor-keys "^4.2.0" + +"@vscode/test-cli@^0.0.10": + version "0.0.10" + resolved "https://registry.yarnpkg.com/@vscode/test-cli/-/test-cli-0.0.10.tgz#35f0e81c2e0ff8daceb223e99d1b65306c15822c" + integrity sha512-B0mMH4ia+MOOtwNiLi79XhA+MLmUItIC8FckEuKrVAVriIuSWjt7vv4+bF8qVFiNFe4QRfzPaIZk39FZGWEwHA== + dependencies: + "@types/mocha" "^10.0.2" + c8 "^9.1.0" + chokidar "^3.5.3" + enhanced-resolve "^5.15.0" + glob "^10.3.10" + minimatch "^9.0.3" + mocha "^10.2.0" + supports-color "^9.4.0" + yargs "^17.7.2" + +"@vscode/test-electron@^2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@vscode/test-electron/-/test-electron-2.4.1.tgz#5c2760640bf692efbdaa18bafcd35fb519688941" + integrity sha512-Gc6EdaLANdktQ1t+zozoBVRynfIsMKMc94Svu1QreOBC8y76x4tvaK32TljrLi1LI2+PK58sDVbL7ALdqf3VRQ== + dependencies: + http-proxy-agent "^7.0.2" + https-proxy-agent "^7.0.5" + jszip "^3.10.1" + ora "^7.0.1" + semver "^7.6.2" + +"@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.14.1.tgz#a9f6a07f2b03c95c8d38c4536a1fdfb521ff55b6" + integrity sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ== + dependencies: + "@webassemblyjs/helper-numbers" "1.13.2" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + +"@webassemblyjs/floating-point-hex-parser@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz#fcca1eeddb1cc4e7b6eed4fc7956d6813b21b9fb" + integrity sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA== + +"@webassemblyjs/helper-api-error@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz#e0a16152248bc38daee76dd7e21f15c5ef3ab1e7" + integrity sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ== + +"@webassemblyjs/helper-buffer@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz#822a9bc603166531f7d5df84e67b5bf99b72b96b" + integrity sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA== + +"@webassemblyjs/helper-numbers@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz#dbd932548e7119f4b8a7877fd5a8d20e63490b2d" + integrity sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.13.2" + "@webassemblyjs/helper-api-error" "1.13.2" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz#e556108758f448aae84c850e593ce18a0eb31e0b" + integrity sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA== + +"@webassemblyjs/helper-wasm-section@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz#9629dda9c4430eab54b591053d6dc6f3ba050348" + integrity sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/wasm-gen" "1.14.1" + +"@webassemblyjs/ieee754@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz#1c5eaace1d606ada2c7fd7045ea9356c59ee0dba" + integrity sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.13.2.tgz#57c5c3deb0105d02ce25fa3fd74f4ebc9fd0bbb0" + integrity sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.13.2.tgz#917a20e93f71ad5602966c2d685ae0c6c21f60f1" + integrity sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ== + +"@webassemblyjs/wasm-edit@^1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz#ac6689f502219b59198ddec42dcd496b1004d597" + integrity sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/helper-wasm-section" "1.14.1" + "@webassemblyjs/wasm-gen" "1.14.1" + "@webassemblyjs/wasm-opt" "1.14.1" + "@webassemblyjs/wasm-parser" "1.14.1" + "@webassemblyjs/wast-printer" "1.14.1" + +"@webassemblyjs/wasm-gen@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz#991e7f0c090cb0bb62bbac882076e3d219da9570" + integrity sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/ieee754" "1.13.2" + "@webassemblyjs/leb128" "1.13.2" + "@webassemblyjs/utf8" "1.13.2" + +"@webassemblyjs/wasm-opt@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz#e6f71ed7ccae46781c206017d3c14c50efa8106b" + integrity sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/wasm-gen" "1.14.1" + "@webassemblyjs/wasm-parser" "1.14.1" + +"@webassemblyjs/wasm-parser@1.14.1", "@webassemblyjs/wasm-parser@^1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz#b3e13f1893605ca78b52c68e54cf6a865f90b9fb" + integrity sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-api-error" "1.13.2" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/ieee754" "1.13.2" + "@webassemblyjs/leb128" "1.13.2" + "@webassemblyjs/utf8" "1.13.2" + +"@webassemblyjs/wast-printer@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz#3bb3e9638a8ae5fdaf9610e7a06b4d9f9aa6fe07" + integrity sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@xtuc/long" "4.2.2" + +"@webpack-cli/configtest@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-3.0.1.tgz#76ac285b9658fa642ce238c276264589aa2b6b57" + integrity sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA== + +"@webpack-cli/info@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-3.0.1.tgz#3cff37fabb7d4ecaab6a8a4757d3826cf5888c63" + integrity sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ== + +"@webpack-cli/serve@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-3.0.1.tgz#bd8b1f824d57e30faa19eb78e4c0951056f72f00" + integrity sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg== + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +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@^8.14.0, acorn@^8.8.2: + version "8.14.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb" + integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg== + +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== + +ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" + +ajv-keywords@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" + integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== + dependencies: + fast-deep-equal "^3.1.3" + +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== + 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" + +ajv@^8.0.0, ajv@^8.9.0: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + +ansi-colors@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== + +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-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-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +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== + +anymatch@^3.0.3, anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +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== + +async@^3.2.3: + version "3.2.6" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== + +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== + dependencies: + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz#9a929eafece419612ef4ae4f60b1862ebad8ef30" + integrity sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== + dependencies: + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + +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== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +bl@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-5.1.0.tgz#183715f678c7188ecef9fe475d90209400624273" + integrity sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ== + dependencies: + buffer "^6.0.3" + inherits "^2.0.4" + readable-stream "^3.4.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browser-stdout@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + +browserslist@^4.24.0: + version "4.24.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.4.tgz#c6b2865a3f08bcb860a0e827389003b9fe686e4b" + integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A== + dependencies: + caniuse-lite "^1.0.30001688" + electron-to-chromium "^1.5.73" + node-releases "^2.0.19" + update-browserslist-db "^1.1.1" + +bs-logger@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + +c8@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/c8/-/c8-9.1.0.tgz#0e57ba3ab9e5960ab1d650b4a86f71e53cb68112" + integrity sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@istanbuljs/schema" "^0.1.3" + find-up "^5.0.0" + foreground-child "^3.1.1" + istanbul-lib-coverage "^3.2.0" + istanbul-lib-report "^3.0.1" + istanbul-reports "^3.1.6" + test-exclude "^6.0.0" + v8-to-istanbul "^9.0.0" + yargs "^17.7.2" + yargs-parser "^21.1.1" + +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@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.0.0, camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001688: + version "1.0.30001707" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001707.tgz#c5e104d199e6f4355a898fcd995a066c7eb9bf41" + integrity sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw== + +chalk@^4.0.0, chalk@^4.0.2, 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== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^5.0.0, chalk@^5.3.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.4.1.tgz#1b48bf0963ec158dce2aacf69c093ae2dd2092d8" + integrity sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w== + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +chokidar@^3.5.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chokidar@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" + integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== + dependencies: + readdirp "^4.0.1" + +chrome-trace-event@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b" + integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +cjs-module-lexer@^1.0.0: + version "1.4.3" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d" + integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== + +cli-cursor@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-4.0.0.tgz#3cecfe3734bf4fe02a8361cbdc0f6fe28c6a57ea" + integrity sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg== + dependencies: + restore-cursor "^4.0.0" + +cli-spinners@^2.9.0: + version "2.9.2" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41" + integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +colorette@^2.0.14: + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + +commander@^12.1.0: + version "12.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" + integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + +cross-spawn@^7.0.3, 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== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@4, debug@^4.1.0, debug@^4.1.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== + dependencies: + ms "^2.1.3" + +decamelize@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" + integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== + +dedent@^1.0.0: + version "1.5.3" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.3.tgz#99aee19eb9bae55a67327717b6e848d0bf777e5a" + integrity sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ== + +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== + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + +diff@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" + integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +ejs@^3.1.10: + version "3.1.10" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== + dependencies: + jake "^10.8.5" + +electron-to-chromium@^1.5.73: + version "1.5.128" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.128.tgz#8ea537b369c32527b3cc47df7973bffe5d3c2980" + integrity sha512-bo1A4HH/NS522Ws0QNFIzyPcyUUNV/yyy70Ho1xqfGYzPUme2F/xr4tlEOuM6/A538U1vDA7a4XfCd1CKRegKQ== + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== + +emoji-regex@^10.2.1: + version "10.4.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.4.0.tgz#03553afea80b3975749cfcb36f776ca268e413d4" + integrity sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +enhanced-resolve@^5.0.0, enhanced-resolve@^5.15.0, enhanced-resolve@^5.17.1: + version "5.18.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz#728ab082f8b7b6836de51f1637aab5d3b9568faf" + integrity sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +envinfo@^7.14.0: + version "7.14.0" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.14.0.tgz#26dac5db54418f2a4c1159153a0b2ae980838aae" + integrity sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg== + +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== + dependencies: + is-arrayish "^0.2.1" + +es-module-lexer@^1.2.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.6.0.tgz#da49f587fd9e68ee2404fe4e256c0c7d3a81be21" + integrity sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ== + +escalade@^3.1.1, escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +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@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.3.0.tgz#10cd3a918ffdd722f5f3f7b5b83db9b23c87340d" + integrity sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +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@^9.21.0: + version "9.23.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.23.0.tgz#b88f3ab6dc83bcb927fdb54407c69ffe5f2441a6" + integrity sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.12.1" + "@eslint/config-array" "^0.19.2" + "@eslint/config-helpers" "^0.2.0" + "@eslint/core" "^0.12.0" + "@eslint/eslintrc" "^3.3.1" + "@eslint/js" "9.23.0" + "@eslint/plugin-kit" "^0.2.7" + "@humanfs/node" "^0.16.6" + "@humanwhocodes/module-importer" "^1.0.1" + "@humanwhocodes/retry" "^0.4.2" + "@types/estree" "^1.0.6" + "@types/json-schema" "^7.0.15" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.6" + debug "^4.3.2" + escape-string-regexp "^4.0.0" + eslint-scope "^8.3.0" + eslint-visitor-keys "^4.2.0" + espree "^10.3.0" + esquery "^1.5.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^8.0.0" + find-up "^5.0.0" + glob-parent "^6.0.2" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + json-stable-stringify-without-jsonify "^1.0.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + +espree@^10.0.1, 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== + dependencies: + acorn "^8.14.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.2.0" + +esprima@^4.0.0: + 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.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + 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: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +events@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^29.0.0, expect@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + dependencies: + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +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.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.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: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fast-uri@^3.0.1: + version "3.0.6" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.6.tgz#88f130b77cfaea2378d56bf970dea21257a68748" + integrity sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw== + +fastest-levenshtein@^1.0.12: + version "1.0.16" + resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" + integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== + +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== + dependencies: + reusify "^1.0.4" + +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +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: + flat-cache "^4.0.0" + +filelist@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + +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== + dependencies: + to-regex-range "^5.0.1" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +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.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@^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== + +foreground-child@^3.1.0, foreground-child@^3.1.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" + integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== + dependencies: + cross-spawn "^7.0.6" + signal-exit "^4.0.1" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.2, fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +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" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +glob-parent@^5.1.2, 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" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +glob@^10.3.10, 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== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + +glob@^7.1.3, glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" + integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== + +graceful-fs@^4.1.2, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.9: + version "4.2.11" + 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== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +he@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +http-proxy-agent@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e" + integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== + dependencies: + agent-base "^7.1.0" + debug "^4.3.4" + +https-proxy-agent@^7.0.5: + version "7.0.6" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9" + integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== + dependencies: + agent-base "^7.1.2" + debug "4" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore@^5.2.0, ignore@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== + +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" + +import-local@^3.0.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +interpret@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" + integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-core-module@^2.16.0: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +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-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-interactive@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-2.0.0.tgz#40c57614593826da1100ade6059778d597f16e90" + integrity sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ== + +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-plain-obj@^2.1.0: + version "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-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +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" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +is-unicode-supported@^1.1.0, is-unicode-supported@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz#d824984b616c292a2e198207d4a609983842f714" + integrity sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ== + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +isexe@^2.0.0: + version "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== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^5.0.4: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-instrument@^6.0.0: + version "6.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-report@^3.0.0, istanbul-lib-report@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3, istanbul-reports@^3.1.6: + version "3.1.7" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" + integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + +jake@^10.8.5: + version "10.9.2" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" + integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.4" + minimatch "^3.1.2" + +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== + dependencies: + execa "^5.0.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^1.0.0" + is-generator-fn "^2.0.0" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + pretty-format "^29.7.0" + pure-rand "^6.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== + dependencies: + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + create-jest "^29.7.0" + exit "^0.1.2" + import-local "^3.0.2" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" + +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== + dependencies: + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== + dependencies: + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" + +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-pnp-resolver "^1.2.2" + jest-util "^29.7.0" + jest-validate "^29.7.0" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== + dependencies: + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.7.0" + graceful-fs "^4.2.9" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + natural-compare "^1.4.0" + pretty-format "^29.7.0" + semver "^7.5.3" + +jest-util@^29.0.0, jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== + dependencies: + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.7.0" + string-length "^4.0.1" + +jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" + import-local "^3.0.2" + jest-cli "^29.7.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== + dependencies: + argparse "^2.0.1" + +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jszip@^3.10.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2" + integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g== + dependencies: + lie "~3.3.0" + pako "~1.0.2" + readable-stream "~2.3.6" + setimmediate "^1.0.5" + +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== + dependencies: + json-buffer "3.0.1" + +kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lie@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" + integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== + dependencies: + immediate "~3.0.5" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +loader-runner@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" + integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +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== + +log-symbols@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +log-symbols@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-5.1.0.tgz#a20e3b9a5f53fac6aeb8e2bb22c07cf2c8f16d93" + integrity sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA== + dependencies: + chalk "^5.0.0" + is-unicode-supported "^1.1.0" + +lru-cache@^10.2.0: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +make-error@^1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0: + 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.0, micromatch@^4.0.4, 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== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.27: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +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== + +minimatch@^3.0.4, 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== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1, minimatch@^5.1.6: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^9.0.3, minimatch@^9.0.4: + 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" + +"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== + +mocha@^10.2.0: + version "10.8.2" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.8.2.tgz#8d8342d016ed411b12a429eb731b825f961afb96" + integrity sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg== + dependencies: + ansi-colors "^4.1.3" + browser-stdout "^1.3.1" + chokidar "^3.5.3" + debug "^4.3.5" + diff "^5.2.0" + escape-string-regexp "^4.0.0" + find-up "^5.0.0" + glob "^8.1.0" + he "^1.2.0" + js-yaml "^4.1.0" + log-symbols "^4.1.0" + minimatch "^5.1.6" + ms "^2.1.3" + serialize-javascript "^6.0.2" + strip-json-comments "^3.1.1" + supports-color "^8.1.1" + workerpool "^6.5.1" + yargs "^16.2.0" + yargs-parser "^20.2.9" + yargs-unparser "^2.0.0" + +mocha@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-11.1.0.tgz#20d7c6ac4d6d6bcb60a8aa47971fca74c65c3c66" + integrity sha512-8uJR5RTC2NgpY3GrYcgpZrsEd9zKbPDpob1RezyR2upGHRQtHWofmzTMzTMSV6dru3tj5Ukt0+Vnq1qhFEEwAg== + dependencies: + ansi-colors "^4.1.3" + browser-stdout "^1.3.1" + chokidar "^3.5.3" + debug "^4.3.5" + diff "^5.2.0" + escape-string-regexp "^4.0.0" + find-up "^5.0.0" + glob "^10.4.5" + he "^1.2.0" + js-yaml "^4.1.0" + log-symbols "^4.1.0" + minimatch "^5.1.6" + ms "^2.1.3" + serialize-javascript "^6.0.2" + strip-json-comments "^3.1.1" + supports-color "^8.1.1" + workerpool "^6.5.1" + yargs "^17.7.2" + yargs-parser "^21.1.1" + yargs-unparser "^2.0.0" + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.19: + version "2.0.19" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" + integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.0, onetime@^5.1.2: + 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.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +ora@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/ora/-/ora-7.0.1.tgz#cdd530ecd865fe39e451a0e7697865669cb11930" + integrity sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw== + dependencies: + chalk "^5.3.0" + cli-cursor "^4.0.0" + cli-spinners "^2.9.0" + is-interactive "^2.0.0" + is-unicode-supported "^1.3.0" + log-symbols "^5.1.0" + stdin-discarder "^0.1.0" + string-width "^6.1.0" + strip-ansi "^7.1.0" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2, p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +package-json-from-dist@^1.0.0: + 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== + +pako@~1.0.2: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + +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@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + +picocolors@^1.0.0, 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.2.1, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pirates@^4.0.4: + version "4.0.7" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" + integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +pretty-format@^29.0.0, pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +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== + +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +pure-rand@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" + integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== + +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" + +react-is@^18.0.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +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== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readable-stream@~2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readdirp@^4.0.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d" + integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +rechoir@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22" + integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== + dependencies: + resolve "^1.20.0" + +require-directory@^2.1.1: + version "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== + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +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-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve.exports@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" + integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== + +resolve@^1.20.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== + dependencies: + is-core-module "^2.16.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +restore-cursor@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-4.0.0.tgz#519560a4318975096def6e609d44100edaa4ccb9" + integrity sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg== + 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== + +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" + +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== + +schema-utils@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.0.tgz#3b669f04f71ff2dfb5aba7ce2d5a9d79b35622c0" + integrity sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g== + dependencies: + "@types/json-schema" "^7.0.9" + ajv "^8.9.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.1.0" + +semver@^6.3.0, semver@^6.3.1: + 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.4, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.2, semver@^7.7.1: + version "7.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" + integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== + +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" + +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: + 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== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== + +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== + +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +stdin-discarder@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/stdin-discarder/-/stdin-discarder-0.1.0.tgz#22b3e400393a8e28ebf53f9958f3880622efde21" + integrity sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ== + dependencies: + bl "^5.0.0" + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^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" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +string-width@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-6.1.0.tgz#96488d6ed23f9ad5d82d13522af9e4c4c3fd7518" + integrity sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^10.2.1" + strip-ansi "^7.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +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" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1, strip-ansi@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +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== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0, supports-color@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-color@^9.4.0: + version "9.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-9.4.0.tgz#17bfcf686288f531db3dea3215510621ccb55954" + integrity sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw== + +supports-preserve-symlinks-flag@^1.0.0: + version "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== + +tapable@^2.1.1, tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +terser-webpack-plugin@^5.3.11: + version "5.3.14" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz#9031d48e57ab27567f02ace85c7d690db66c3e06" + integrity sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.25" + jest-worker "^27.4.5" + schema-utils "^4.3.0" + serialize-javascript "^6.0.2" + terser "^5.31.1" + +terser@^5.31.1: + version "5.39.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.39.0.tgz#0e82033ed57b3ddf1f96708d123cca717d86ca3a" + integrity sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw== + dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" + commander "^2.20.0" + source-map-support "~0.5.20" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +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" + +ts-api-utils@^2.0.1: + 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-jest@^29.3.0: + version "29.3.1" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.3.1.tgz#2e459e1f94a833bd8216ba4b045fac948e265937" + integrity sha512-FT2PIRtZABwl6+ZCry8IY7JZ3xMuppsEV9qFVHOVe8jDzggwUZ9TsM4chyJxL9yi6LvkqcZYU3LmapEE454zBQ== + dependencies: + bs-logger "^0.2.6" + ejs "^3.1.10" + fast-json-stable-stringify "^2.1.0" + jest-util "^29.0.0" + json5 "^2.2.3" + lodash.memoize "^4.1.2" + make-error "^1.3.6" + semver "^7.7.1" + type-fest "^4.38.0" + yargs-parser "^21.1.1" + +ts-loader@^9.5.2: + version "9.5.2" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.5.2.tgz#1f3d7f4bb709b487aaa260e8f19b301635d08020" + integrity sha512-Qo4piXvOTWcMGIgRiuFa6nHNm+54HbYaZCKqc9eeZCLRy3XqafQgwX2F7mofrbJG3g7EEb+lkiR+z2Lic2s3Zw== + dependencies: + chalk "^4.1.0" + enhanced-resolve "^5.0.0" + micromatch "^4.0.0" + semver "^7.3.4" + source-map "^0.7.4" + +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" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +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@^4.38.0: + version "4.38.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.38.0.tgz#659fa14d1a71c2811400aa3b5272627e0c1e6b96" + integrity sha512-2dBz5D5ycHIoliLYLi0Q2V7KRaDlH0uWIvmk7TYlAg5slqwiPv1ezJdZm1QEM0xgk29oYWMCbIG7E6gHpvChlg== + +typescript@^5.7.3: + version "5.8.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.2.tgz#8170b3702f74b79db2e5a96207c15e65807999e4" + integrity sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ== + +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + +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== + +update-browserslist-db@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" + integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +v8-to-istanbul@^9.0.0, v8-to-istanbul@^9.0.1: + version "9.3.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" + integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +watchpack@^2.4.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.2.tgz#2feeaed67412e7c33184e5a79ca738fbd38564da" + integrity sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +webpack-cli@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-6.0.1.tgz#a1ce25da5ba077151afd73adfa12e208e5089207" + integrity sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw== + dependencies: + "@discoveryjs/json-ext" "^0.6.1" + "@webpack-cli/configtest" "^3.0.1" + "@webpack-cli/info" "^3.0.1" + "@webpack-cli/serve" "^3.0.1" + colorette "^2.0.14" + commander "^12.1.0" + cross-spawn "^7.0.3" + envinfo "^7.14.0" + fastest-levenshtein "^1.0.12" + import-local "^3.0.2" + interpret "^3.1.1" + rechoir "^0.8.0" + webpack-merge "^6.0.1" + +webpack-merge@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-6.0.1.tgz#50c776868e080574725abc5869bd6e4ef0a16c6a" + integrity sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg== + dependencies: + clone-deep "^4.0.1" + flat "^5.0.2" + wildcard "^2.0.1" + +webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== + +webpack@^5.98.0: + version "5.98.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.98.0.tgz#44ae19a8f2ba97537978246072fb89d10d1fbd17" + integrity sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA== + dependencies: + "@types/eslint-scope" "^3.7.7" + "@types/estree" "^1.0.6" + "@webassemblyjs/ast" "^1.14.1" + "@webassemblyjs/wasm-edit" "^1.14.1" + "@webassemblyjs/wasm-parser" "^1.14.1" + acorn "^8.14.0" + browserslist "^4.24.0" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.17.1" + es-module-lexer "^1.2.1" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.11" + json-parse-even-better-errors "^2.3.1" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^4.3.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.3.11" + watchpack "^2.4.1" + webpack-sources "^3.2.3" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wildcard@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" + integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== + +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@^6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" + integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yargs-parser@^20.2.2, yargs-parser@^20.2.9: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs-unparser@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" + integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== + dependencies: + camelcase "^6.0.0" + decamelize "^4.0.0" + flat "^5.0.2" + is-plain-obj "^2.1.0" + +yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yargs@^17.3.1, yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/patched-vscode/extensions/sagemaker-extension/package.json b/patched-vscode/extensions/sagemaker-extension/package.json index 88e8cc0e5..0614b6b95 100644 --- a/patched-vscode/extensions/sagemaker-extension/package.json +++ b/patched-vscode/extensions/sagemaker-extension/package.json @@ -27,8 +27,14 @@ "contributes": { "configuration": { "type": "object", - "title": "Sagemaker Extension", - "properties": {} + "title": "SageMaker Extension", + "properties": { + "sagemaker-extension.notification.extensionAutoUpdateDisabled": { + "type": "boolean", + "default": true, + "markdownDescription": "Show notification if extension auto update is disabled" + } + } }, "commands": [ ] diff --git a/patched-vscode/extensions/sagemaker-extension/src/constant.ts b/patched-vscode/extensions/sagemaker-extension/src/constant.ts index 3417eefb0..ea1d482e4 100644 --- a/patched-vscode/extensions/sagemaker-extension/src/constant.ts +++ b/patched-vscode/extensions/sagemaker-extension/src/constant.ts @@ -27,6 +27,10 @@ export const FIVE_MINUTES_INTERVAL_MILLIS = 5 * 60 * 1000; export const SAGEMAKER_METADATA_PATH = '/opt/ml/metadata/resource-metadata.json'; +// Service name identifier for SageMaker Unified Studio +export const SMUS_SERVICE_NAME = 'SageMakerUnifiedStudio'; +export const SERVICE_NAME_ENV_VAR = 'SERVICE_NAME'; + export class SagemakerCookie { authMode: string expiryTime: number @@ -56,6 +60,11 @@ export class SagemakerResourceMetadata { ResourceArn?: string ResourceName?: string AppImageVersion?: string + AdditionalMetadata?: { + DataZoneDomainId?: string + DataZoneProjectId?: string + DataZoneDomainRegion?: string + } }; export function isSSOMode(cookie: SagemakerCookie) { return (cookie.authMode === AUTH_MODE.SSO) @@ -69,4 +78,35 @@ export function getExpiryTime(cookie: SagemakerCookie): number { } else { return -1; } -} \ No newline at end of file +} + +/** + * Constructs the SMUS portal URL using domain, region, and project information + * Returns null if not in SMUS environment or if required fields are missing + */ +export const getSmusVscodePortalUrl = (metadata: SagemakerResourceMetadata | null): string | null => { + if (process.env[SERVICE_NAME_ENV_VAR] !== SMUS_SERVICE_NAME) { + return null; + } + + if (!metadata || !metadata.AdditionalMetadata) { + // fail silently not to block users + console.error('[SMUS] Metadata is undefined or null'); + return null; + } + + const { DataZoneDomainId, DataZoneDomainRegion, DataZoneProjectId } = metadata.AdditionalMetadata; + + if (!DataZoneDomainId || !DataZoneDomainRegion || !DataZoneProjectId) { + // fail silently not to block users + // TODO: add monitoring to detect such cases + console.error('[SMUS] Required fields missing in metadata:', { + DataZoneDomainId: !!DataZoneDomainId, + DataZoneDomainRegion: !!DataZoneDomainRegion, + DataZoneProjectId: !!DataZoneProjectId + }); + return null; + } + + return `https://${DataZoneDomainId}.sagemaker.${DataZoneDomainRegion}.on.aws/projects/${DataZoneProjectId}/overview`; +} diff --git a/patched-vscode/extensions/sagemaker-extension/src/extension.ts b/patched-vscode/extensions/sagemaker-extension/src/extension.ts index b85cec6ad..e40febdd7 100644 --- a/patched-vscode/extensions/sagemaker-extension/src/extension.ts +++ b/patched-vscode/extensions/sagemaker-extension/src/extension.ts @@ -11,12 +11,32 @@ import { WARNING_BUTTON_SAVE_AND_RENEW_SESSION, SagemakerCookie, SagemakerResourceMetadata, - getExpiryTime + getExpiryTime, + getSmusVscodePortalUrl } from "./constant"; import * as console from "console"; const PARSE_SAGEMAKER_COOKIE_COMMAND = 'sagemaker.parseCookies'; +const ENABLE_AUTO_UPDATE_COMMAND = 'workbench.extensions.action.enableAutoUpdate'; + +// Global redirect URL for SMUS environment +let smusRedirectUrl: string | null = null; + +function fetchMetadata(): SagemakerResourceMetadata | null { + try { + const data = fs.readFileSync(SAGEMAKER_METADATA_PATH, 'utf-8'); + return JSON.parse(data) as SagemakerResourceMetadata; + } catch (error) { + // fail silently not to block users + console.error('Error reading metadata file:', error); + return null; + } +} + +function initializeSmusRedirectUrl() { + smusRedirectUrl = getSmusVscodePortalUrl(fetchMetadata()); +} function showWarningDialog() { vscode.commands.executeCommand(PARSE_SAGEMAKER_COOKIE_COMMAND).then(response => { @@ -58,11 +78,12 @@ function showWarningDialog() { } function signInError(sagemakerCookie: SagemakerCookie) { + const redirectUrl = getRedirectUrl(sagemakerCookie); // The session has expired SessionWarning.signInWarning(sagemakerCookie) .then((selection) => { if (selection === SIGN_IN_BUTTON) { - vscode.env.openExternal(vscode.Uri.parse(sagemakerCookie.redirectURL)); + vscode.env.openExternal(vscode.Uri.parse(redirectUrl)); } }); } @@ -93,32 +114,52 @@ function saveWorkspace() { }); } function renewSession(sagemakerCookie: SagemakerCookie) { + const redirectUrl = getRedirectUrl(sagemakerCookie); // TODO: Log and trigger a Signin - vscode.env.openExternal(vscode.Uri.parse(sagemakerCookie.redirectURL)); + vscode.env.openExternal(vscode.Uri.parse(redirectUrl)); // Trigger the function to show the warning again after 5 minutes again to validate. setTimeout(showWarningDialog, FIVE_MINUTES_INTERVAL_MILLIS); } function updateStatusItemWithMetadata(context: vscode.ExtensionContext) { - fs.readFile(SAGEMAKER_METADATA_PATH, 'utf-8', (err, data) => { - if (err) { - // fail silently not to block users - } else { - try { - const jsonData = JSON.parse(data) as SagemakerResourceMetadata; - const spaceName = jsonData.SpaceName; - - if (spaceName != null) { - let spaceNameStatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100); - spaceNameStatusBarItem.text = `Space: ${spaceName}`; - spaceNameStatusBarItem.show(); - context.subscriptions.push(spaceNameStatusBarItem); - } - } catch (jsonError) { - // fail silently not to block users + const metadata = fetchMetadata(); + if (metadata?.SpaceName) { + let spaceNameStatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100); + spaceNameStatusBarItem.text = `Space: ${metadata.SpaceName}`; + spaceNameStatusBarItem.show(); + context.subscriptions.push(spaceNameStatusBarItem); + } +} + +// Render warning message regarding auto upgrade disabled +function renderExtensionAutoUpgradeDisabledNotification() { + // Get current extension auto disabled config + const autoUpdateEnabled = vscode.workspace.getConfiguration('extensions').get('autoUpdate'); + + // Check if customer has choose to disable this notification + const extensionConfig = vscode.workspace.getConfiguration('sagemaker-extension'); + const showNotificationEnabled = extensionConfig.get('notification.extensionAutoUpdateDisabled', true); + + // Only show notification, if auto update is disabled, and customer hasn't opt-out the notification + if (showNotificationEnabled && autoUpdateEnabled == false) { + const enableAutoUpdate = 'Enable Auto Update Extensions'; + const doNotShowAgain = 'Do not show again'; + vscode.window.showInformationMessage( + 'Extension auto-update is disabled. This can be changed in Code Editor settings.', + enableAutoUpdate, + doNotShowAgain, + ).then(response => { + if (response === enableAutoUpdate) { + vscode.commands.executeCommand(ENABLE_AUTO_UPDATE_COMMAND) + } else if (response == doNotShowAgain) { + extensionConfig.update( + 'notification.extensionAutoUpdateDisabled', + false, + vscode.ConfigurationTarget.Global + ); } - } - }); + }) + } } export function activate(context: vscode.ExtensionContext) { @@ -126,6 +167,9 @@ export function activate(context: vscode.ExtensionContext) { // TODO: log activation of extension console.log('Activating Sagemaker Extension...'); + // First set smusRedirectUrl if we are in SMUS environment + initializeSmusRedirectUrl(); + // execute the get cookie command and save the data to cookies vscode.commands.executeCommand(PARSE_SAGEMAKER_COOKIE_COMMAND).then(r => { @@ -134,4 +178,15 @@ export function activate(context: vscode.ExtensionContext) { initialize(sagemakerCookie); updateStatusItemWithMetadata(context); }); + + // render warning message regarding auto upgrade disabled + renderExtensionAutoUpgradeDisabledNotification(); +} + +/** + * Returns the appropriate redirect URL based on the environment + * Uses SMUS URL if available, falls back to original redirect URL + */ +function getRedirectUrl(sagemakerCookie: SagemakerCookie): string { + return smusRedirectUrl || sagemakerCookie.redirectURL; } diff --git a/patched-vscode/extensions/sagemaker-extensions-sync/.vscodeignore b/patched-vscode/extensions/sagemaker-extensions-sync/.vscodeignore new file mode 100644 index 000000000..56b78554c --- /dev/null +++ b/patched-vscode/extensions/sagemaker-extensions-sync/.vscodeignore @@ -0,0 +1,12 @@ +.vscode/** +.vscode-test/** +out/test/** +out/** +test/** +src/** +tsconfig.json +out/test/** +out/** +cgmanifest.json +yarn.lock +preview-src/** diff --git a/patched-vscode/extensions/sagemaker-extensions-sync/README.md b/patched-vscode/extensions/sagemaker-extensions-sync/README.md new file mode 100644 index 000000000..b8dd030e4 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-extensions-sync/README.md @@ -0,0 +1,3 @@ +# SageMaker Code Editor Extensions Sync + +Notifies users if the extensions directory is missing pre-packaged extensions from SageMaker Distribution and give them the option to sync them. \ No newline at end of file diff --git a/patched-vscode/extensions/sagemaker-extensions-sync/extension-browser.webpack.config.js b/patched-vscode/extensions/sagemaker-extensions-sync/extension-browser.webpack.config.js new file mode 100644 index 000000000..68271e0e9 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-extensions-sync/extension-browser.webpack.config.js @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright Amazon.com Inc. or its affiliates. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check + +'use strict'; + +const withBrowserDefaults = require('../shared.webpack.config').browser; + +module.exports = withBrowserDefaults({ + context: __dirname, + entry: { + extension: './src/extension.ts' + }, +}); diff --git a/patched-vscode/extensions/sagemaker-extensions-sync/extension.webpack.config.js b/patched-vscode/extensions/sagemaker-extensions-sync/extension.webpack.config.js new file mode 100644 index 000000000..598526267 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-extensions-sync/extension.webpack.config.js @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright Amazon.com Inc. or its affiliates. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check + +'use strict'; + +const withDefaults = require('../shared.webpack.config'); + +module.exports = withDefaults({ + context: __dirname, + resolve: { + mainFields: ['module', 'main'] + }, + entry: { + extension: './src/extension.ts', + } +}); diff --git a/patched-vscode/extensions/sagemaker-extensions-sync/package.json b/patched-vscode/extensions/sagemaker-extensions-sync/package.json new file mode 100644 index 000000000..a0761fa03 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-extensions-sync/package.json @@ -0,0 +1,44 @@ +{ + "name": "sagemaker-extensions-sync", + "displayName": "SageMaker Extensions Sync", + "description": "Sync pre-packaged extensions from SageMaker Distribution", + "extensionKind": [ + "workspace" + ], + "version": "1.0.0", + "publisher": "sagemaker", + "license": "MIT", + "engines": { + "vscode": "^1.70.0" + }, + "main": "./out/extension", + "categories": [ + "Other" + ], + "activationEvents": [ + "*" + ], + "capabilities": { + "virtualWorkspaces": true, + "untrustedWorkspaces": { + "supported": true + } + }, + "contributes": { + "commands": [ + { + "command": "extensions-sync.syncExtensions", + "title": "Sync Extensions from SageMaker Distribution", + "category": "Extensions Sync" + } + ] + }, + "scripts": { + "compile": "gulp compile-extension:sagemaker-extensions-sync", + "watch": "npm run build-preview && gulp watch-extension:sagemaker-extensions-sync", + "vscode:prepublish": "npm run build-ext", + "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:sagemaker-idle-extension ./tsconfig.json" + }, + "dependencies": {}, + "repository": {} +} diff --git a/patched-vscode/extensions/sagemaker-extensions-sync/src/constants.ts b/patched-vscode/extensions/sagemaker-extensions-sync/src/constants.ts new file mode 100644 index 000000000..1a7fdcb84 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-extensions-sync/src/constants.ts @@ -0,0 +1,21 @@ +// constants +export const PERSISTENT_VOLUME_EXTENSIONS_DIR = "/home/sagemaker-user/sagemaker-code-editor-server-data/extensions"; +export const IMAGE_EXTENSIONS_DIR = "/opt/amazon/sagemaker/sagemaker-code-editor-server-data/extensions"; +export const LOG_PREFIX = "[sagemaker-extensions-sync]"; + +export class ExtensionInfo { + constructor( + public name: string, + public publisher: string, + public version: string, + public path: string | null + ) {} + + get identifier(): string { + return `${this.publisher}.${this.name}@${this.version}`; + } + + toString(): string { + return `ExtensionInfo: ${this.identifier} (${this.path})`; + } +} diff --git a/patched-vscode/extensions/sagemaker-extensions-sync/src/extension.ts b/patched-vscode/extensions/sagemaker-extensions-sync/src/extension.ts new file mode 100644 index 000000000..f9f44fd56 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-extensions-sync/src/extension.ts @@ -0,0 +1,100 @@ +import * as process from "process"; +import * as vscode from 'vscode'; + +import { + ExtensionInfo, + IMAGE_EXTENSIONS_DIR, + LOG_PREFIX, + PERSISTENT_VOLUME_EXTENSIONS_DIR, +} from "./constants" + +import { + getExtensionsFromDirectory, + getInstalledExtensions, + installExtension, + refreshExtensionsMetadata } from "./utils" + +export async function activate() { + + // this extension will only activate within a sagemaker app + const isSageMakerApp = !!process.env?.SAGEMAKER_APP_TYPE_LOWERCASE; + if (!isSageMakerApp) { + return; + } + + // get installed extensions. this could be different from pvExtensions b/c vscode sometimes doesn't delete the assets + // for an old extension when uninstalling or changing versions + const installedExtensions = new Set(await getInstalledExtensions()); + console.log(`${LOG_PREFIX} Found installed extensions: `, Array.from(installedExtensions)); + + const prePackagedExtensions: ExtensionInfo[] = await getExtensionsFromDirectory(IMAGE_EXTENSIONS_DIR); + const prePackagedExtensionsById: Record = {}; + prePackagedExtensions.forEach(extension => { + prePackagedExtensionsById[extension.identifier] = extension; + }); + + console.log(`${LOG_PREFIX} Found pre-packaged extensions: `, prePackagedExtensions); + + const pvExtensions = await getExtensionsFromDirectory(PERSISTENT_VOLUME_EXTENSIONS_DIR); + const pvExtensionsByName: Record = {}; + const pvExtensionsById: Record = {}; + pvExtensions.forEach(extension => { + if (installedExtensions.has(extension.identifier)) { // only index extensions that are installed + pvExtensionsByName[extension.name] = extension; + pvExtensionsById[extension.identifier] = extension; + } + }); + console.log(`${LOG_PREFIX} Found installed extensions in persistent volume: `, pvExtensionsById); + + // check each pre-packaged extension, record if it is not in installed extensions or version mismatch + // store unsynced extensions as {identifier pre-packaged ext: currently installed version} + const unsyncedExtensions: Record = {} + prePackagedExtensions.forEach(extension => { + const id = extension.identifier; + if (!(installedExtensions.has(id))){ + unsyncedExtensions[id] = pvExtensionsByName[extension.name]?.version ?? null; + } + }); + console.log(`${LOG_PREFIX} Unsynced extensions: `, unsyncedExtensions); + + if (Object.keys(unsyncedExtensions).length !== 0) { + const selection = await vscode.window.showWarningMessage( + 'Warning: You have unsynchronized extensions from SageMaker Distribution \ + which could result in incompatibilities with Code Editor. Do you want to install them?', + "Synchronize Extensions", "Dismiss"); + + if (selection === "Synchronize Extensions") { + const quickPick = vscode.window.createQuickPick(); + quickPick.items = Object.keys(unsyncedExtensions).map(extensionId => ({ + label: extensionId, + description: unsyncedExtensions[extensionId] ? `Currently installed version: ${unsyncedExtensions[extensionId]}` : undefined, + })); + quickPick.placeholder = 'Select extensions to install'; + quickPick.canSelectMany = true; + quickPick.ignoreFocusOut = true; + + quickPick.onDidAccept(async () => { + const selectedExtensions = quickPick.selectedItems.map(item => item.label); + + for (const extensionId of selectedExtensions) { + const extensionName = prePackagedExtensionsById[extensionId].name; + await installExtension(prePackagedExtensionsById[extensionId], pvExtensionsByName[extensionName]); + } + await refreshExtensionsMetadata(); + + quickPick.hide(); + await vscode.window.showInformationMessage( + 'Extensions have been installed. \nWould you like to reload the window?', + { modal: true }, + 'Reload' + ).then(selection => { + if (selection === 'Reload') { + vscode.commands.executeCommand('workbench.action.reloadWindow'); + } + }); + }); + + quickPick.show(); + } + } +} \ No newline at end of file diff --git a/patched-vscode/extensions/sagemaker-extensions-sync/src/utils.ts b/patched-vscode/extensions/sagemaker-extensions-sync/src/utils.ts new file mode 100644 index 000000000..e2d34fe06 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-extensions-sync/src/utils.ts @@ -0,0 +1,152 @@ +import * as fs from "fs/promises"; +import * as path from "path"; +import * as vscode from 'vscode'; +import { execFile } from "child_process"; +import { promisify } from "util"; + +import { + ExtensionInfo, + LOG_PREFIX, + PERSISTENT_VOLUME_EXTENSIONS_DIR, +} from "./constants" + +export async function getExtensionsFromDirectory(directoryPath: string): Promise { + const results: ExtensionInfo[] = []; + try { + const items = await fs.readdir(directoryPath); + + for (const item of items) { + const itemPath = path.join(directoryPath, item); + try { + const stats = await fs.stat(itemPath); + + if (stats.isDirectory()) { + const packageJsonPath = path.join(itemPath, "package.json"); + + const packageData = JSON.parse(await fs.readFile(packageJsonPath, "utf8")); + + if (packageData.name && packageData.publisher && packageData.version) { + results.push(new ExtensionInfo( + packageData.name, + packageData.publisher, + packageData.version, + itemPath, + )); + } + } + } catch (error) { + // fs.stat will break on dangling simlinks. Just skip to the next file + console.error(`${LOG_PREFIX} Error reading package.json in ${itemPath}:`, error); + } + } + } catch (error) { + console.error(`${LOG_PREFIX} Error reading directory ${directoryPath}:`, error); + } + return results; +} + +export async function getInstalledExtensions(): Promise { + const command = "sagemaker-code-editor"; + const args = ["--list-extensions", "--show-versions", "--extensions-dir", PERSISTENT_VOLUME_EXTENSIONS_DIR]; + + const execFileAsync = promisify(execFile); + try { + const { stdout, stderr } = await execFileAsync(command, args); + if (stderr) { + throw new Error("stderr"); + } + return stdout.split("\n").filter(line => line.trim() !== ""); + } catch (error) { + console.error(`${LOG_PREFIX} Error getting list of installed extensions:`, error); + throw error; + } +} + +export async function refreshExtensionsMetadata(): Promise { + const metaDataFile = path.join(PERSISTENT_VOLUME_EXTENSIONS_DIR, "extensions.json"); + try { + await fs.unlink(metaDataFile); + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { + console.error(`${LOG_PREFIX} Error removing metadata file:`, error); + } + } +} + +export async function installExtension( + prePackagedExtensionInfo: ExtensionInfo, installedExtensionInfo?: ExtensionInfo | undefined +): Promise { + if (installedExtensionInfo) { + console.log(`${LOG_PREFIX} Upgrading extension from ${installedExtensionInfo.identifier} to ${prePackagedExtensionInfo.identifier}`); + } else { + console.log(`${LOG_PREFIX} Installing extension ${prePackagedExtensionInfo.identifier}`); + } + try { + if (!prePackagedExtensionInfo.path) { + throw new Error(`Extension path missing for ${prePackagedExtensionInfo.identifier}`); + } + + const targetPath = path.join(PERSISTENT_VOLUME_EXTENSIONS_DIR, path.basename(prePackagedExtensionInfo.path)); + + // Remove existing symlink or directory if it exists + try { + console.log(`${LOG_PREFIX} Removing existing folder ${targetPath}`); + await fs.unlink(targetPath); + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { + console.error(`${LOG_PREFIX} Error removing existing extension:`, error); + throw error; + } + // if file already doesn't exist then keep going + } + + // Create new symlink + try { + console.log(`${LOG_PREFIX} Adding extension to persistent volume directory`); + await fs.symlink(prePackagedExtensionInfo.path, targetPath, 'dir'); + } catch (error) { + console.error(`${LOG_PREFIX} Error adding extension to persistent volume directory:`, error); + throw error; + } + + // Handle .obsolete file + const OBSOLETE_FILE = path.join(PERSISTENT_VOLUME_EXTENSIONS_DIR, '.obsolete'); + let obsoleteData: Record = {}; + + try { + const obsoleteContent = await fs.readFile(OBSOLETE_FILE, 'utf-8'); + console.log(`${LOG_PREFIX} .obsolete file found`); + obsoleteData = JSON.parse(obsoleteContent); + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') { + console.log(`${LOG_PREFIX} .obsolete file not found. Creating a new one.`); + } else { + console.warn(`${LOG_PREFIX} Error reading .obsolete file:`, error); + // Backup malformed file + const backupPath = `${OBSOLETE_FILE}.bak`; + await fs.rename(OBSOLETE_FILE, backupPath); + console.log(`${LOG_PREFIX} Backed up malformed .obsolete file to ${backupPath}`); + } + } + + if (installedExtensionInfo?.path) { + const obsoleteBasename = path.basename(installedExtensionInfo.path); + obsoleteData[obsoleteBasename] = true; + } + const obsoleteBasenamePrepackaged = path.basename(prePackagedExtensionInfo.path); + obsoleteData[obsoleteBasenamePrepackaged] = false; + + try { + console.log(`${LOG_PREFIX} Writing to .obsolete file.`); + await fs.writeFile(OBSOLETE_FILE, JSON.stringify(obsoleteData, null, 2)); + } catch (error) { + console.error(`${LOG_PREFIX} Error writing .obsolete file:`, error); + throw error; + } + + console.log(`${LOG_PREFIX} Installed ${prePackagedExtensionInfo.identifier}`); + } catch (error) { + vscode.window.showErrorMessage(`Could not install extension ${prePackagedExtensionInfo.identifier}`); + console.error(`${LOG_PREFIX} ${error}`); + } +} \ No newline at end of file diff --git a/patched-vscode/extensions/sagemaker-extensions-sync/tsconfig.json b/patched-vscode/extensions/sagemaker-extensions-sync/tsconfig.json new file mode 100644 index 000000000..e474d9a56 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-extensions-sync/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "outDir": "./out" + }, + "include": [ + "../sagemaker-extensions-sync/src/**/*", + "../../src/vscode-dts/vscode.d.ts" + ] +} diff --git a/patched-vscode/extensions/sagemaker-extensions-sync/yarn.lock b/patched-vscode/extensions/sagemaker-extensions-sync/yarn.lock new file mode 100644 index 000000000..fb57ccd13 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-extensions-sync/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + diff --git a/patched-vscode/extensions/sagemaker-idle-extension/src/extension.ts b/patched-vscode/extensions/sagemaker-idle-extension/src/extension.ts index 0fbb35e6e..2a11ca447 100644 --- a/patched-vscode/extensions/sagemaker-idle-extension/src/extension.ts +++ b/patched-vscode/extensions/sagemaker-idle-extension/src/extension.ts @@ -21,15 +21,11 @@ export function deactivate() { /** * Initializes the file path where the idle timestamp will be stored. - * It sets the path to a hidden file in the user's home directory. + * It sets the path to a hidden file in the /tmp/ directory. */ function initializeIdleFilePath() { - const homeDirectory = process.env.HOME || process.env.USERPROFILE; - if (!homeDirectory) { - console.log(`${LOG_PREFIX} Unable to determine the home directory.`); - return; - } - idleFilePath = path.join(homeDirectory, ".sagemaker-last-active-timestamp"); + const tmpDirectory = "/tmp/"; + idleFilePath = path.join(tmpDirectory, ".sagemaker-last-active-timestamp"); // Set initial lastActivetimestamp updateLastActivityTimestamp() @@ -70,13 +66,13 @@ const startMonitoringTerminalActivity = () => { /** * Checks for terminal activity by reading the /dev/pts directory and comparing modification times of the files. - * + * * The /dev/pts directory is used in Unix-like operating systems to represent pseudo-terminal (PTY) devices. - * Each active terminal session is assigned a PTY device. These devices are represented as files within the /dev/pts directory. - * When a terminal session has activity, such as when a user inputs commands or output is written to the terminal, - * the modification time (mtime) of the corresponding PTY device file is updated. By monitoring the modification + * Each active terminal session is assigned a PTY device. These devices are represented as files within the /dev/pts directory. + * When a terminal session has activity, such as when a user inputs commands or output is written to the terminal, + * the modification time (mtime) of the corresponding PTY device file is updated. By monitoring the modification * times of the files in the /dev/pts directory, we can detect terminal activity. - * + * * If activity is detected (i.e., if any PTY device file was modified within the CHECK_INTERVAL), this function * updates the last activity timestamp. */ @@ -95,7 +91,7 @@ const checkTerminalActivity = () => { const mtime = new Date(stats.mtime).getTime(); return now - mtime < CHECK_INTERVAL; } catch (error) { - console.error(`${LOG_PREFIX}}Error reading file stats:`, error); + console.error(`${LOG_PREFIX} Error reading file stats:`, error); return false; } }); @@ -106,7 +102,7 @@ const checkTerminalActivity = () => { }); }; - /** +/** * Updates the last activity timestamp by recording the current timestamp in the idle file and * refreshing the status bar. The timestamp should be in ISO 8601 format and set to the UTC timezone. */ diff --git a/patched-vscode/extensions/sagemaker-open-notebook-extension/.vscodeignore b/patched-vscode/extensions/sagemaker-open-notebook-extension/.vscodeignore new file mode 100644 index 000000000..56b78554c --- /dev/null +++ b/patched-vscode/extensions/sagemaker-open-notebook-extension/.vscodeignore @@ -0,0 +1,12 @@ +.vscode/** +.vscode-test/** +out/test/** +out/** +test/** +src/** +tsconfig.json +out/test/** +out/** +cgmanifest.json +yarn.lock +preview-src/** diff --git a/patched-vscode/extensions/sagemaker-open-notebook-extension/README.md b/patched-vscode/extensions/sagemaker-open-notebook-extension/README.md new file mode 100644 index 000000000..0efc94ac6 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-open-notebook-extension/README.md @@ -0,0 +1,18 @@ +# Code Editor Open Notebook Extension + +The Open Notebook extension enables users to download, transform, and display sample notebooks from a public Amazon S3 bucket owned by the SageMaker team. This extension streamlines the process of accessing and working with SageMaker sample notebooks directly within Code Editor. + +## Features + +- Download sample notebooks from a specified S3 bucket +- Transform notebooks for compatibility with VSCode +- Display notebooks within the Code Editor environment +- Utilize URL parameters to open specific notebooks + +## Usage + +The extension uses parameters from the URL to open the desired notebook. The required parameters are: +- Notebook key: The identifier for the specific notebook in the S3 bucket +- Cluster ID: The ID of the SageMaker cluster +- Region: The AWS region where the S3 bucket is located + diff --git a/patched-vscode/extensions/sagemaker-open-notebook-extension/extension-browser.webpack.config.js b/patched-vscode/extensions/sagemaker-open-notebook-extension/extension-browser.webpack.config.js new file mode 100644 index 000000000..68271e0e9 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-open-notebook-extension/extension-browser.webpack.config.js @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright Amazon.com Inc. or its affiliates. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check + +'use strict'; + +const withBrowserDefaults = require('../shared.webpack.config').browser; + +module.exports = withBrowserDefaults({ + context: __dirname, + entry: { + extension: './src/extension.ts' + }, +}); diff --git a/patched-vscode/extensions/sagemaker-open-notebook-extension/extension.webpack.config.js b/patched-vscode/extensions/sagemaker-open-notebook-extension/extension.webpack.config.js new file mode 100644 index 000000000..598526267 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-open-notebook-extension/extension.webpack.config.js @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright Amazon.com Inc. or its affiliates. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check + +'use strict'; + +const withDefaults = require('../shared.webpack.config'); + +module.exports = withDefaults({ + context: __dirname, + resolve: { + mainFields: ['module', 'main'] + }, + entry: { + extension: './src/extension.ts', + } +}); diff --git a/patched-vscode/extensions/sagemaker-open-notebook-extension/package.json b/patched-vscode/extensions/sagemaker-open-notebook-extension/package.json new file mode 100644 index 000000000..6a62df9cd --- /dev/null +++ b/patched-vscode/extensions/sagemaker-open-notebook-extension/package.json @@ -0,0 +1,44 @@ +{ + "name": "sagemaker-open-notebook-extension", + "displayName": "Sagemaker open notebook Extension", + "description": "To download and open sample notebook when open code editor", + "extensionKind": [ + "workspace" + ], + "version": "1.0.0", + "publisher": "sagemaker", + "license": "MIT", + "engines": { + "vscode": "^1.70.0" + }, + "main": "./out/extension", + "categories": [ + "Other" + ], + "activationEvents": [ + "*" + ], + "capabilities": { + "virtualWorkspaces": true, + "untrustedWorkspaces": { + "supported": true + } + }, + "contributes": { + "configuration": { + "type": "object", + "title": "Sagemaker Open Notebook Extension", + "properties": {} + }, + "commands": [ + ] + }, + "scripts": { + "compile": "gulp compile-extension:sagemaker-open-notebook-extension", + "watch": "npm run build-preview && gulp watch-extension:sagemaker-open-notebook-extension", + "vscode:prepublish": "npm run build-ext", + "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:sagemaker-open-notebook-extension ./tsconfig.json" + }, + "dependencies": {}, + "repository": {} +} diff --git a/patched-vscode/extensions/sagemaker-open-notebook-extension/src/extension.ts b/patched-vscode/extensions/sagemaker-open-notebook-extension/src/extension.ts new file mode 100644 index 000000000..024d5504a --- /dev/null +++ b/patched-vscode/extensions/sagemaker-open-notebook-extension/src/extension.ts @@ -0,0 +1,100 @@ + +import * as vscode from 'vscode'; +import * as https from 'https'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; +import * as console from 'console'; + +export function activate() { + const config = vscode.workspace.getConfiguration('extensions.openNotebookData'); + const notebookKey = config.get('notebookKey') as string; + const clusterId = config.get('clusterId') as string; + const region = config.get('region') as string; + if(notebookKey){ + loadAndDisplayNotebook(notebookKey, clusterId, region); + } + +} + +function isValidRegion(region: string): boolean { + // This regex allows for characters, numbers, and hyphens + const regionRegex = /^[a-zA-Z0-9-]+$/; + return regionRegex.test(region); +} + +async function loadAndDisplayNotebook(fileKey: string, clusterId: string, region: string) { + if (!isValidRegion(region)) { + vscode.window.showErrorMessage('Invalid region format. Region should only contain characters, numbers, and hyphens.'); + return; + } + + const bucketName = `jumpstart-cache-prod-${region}`; + const url = `https://${bucketName}.s3.${region}.amazonaws.com/${fileKey}`; + try { + let content = await downloadFile(url); + content = processNotebookContent(content, clusterId, region); + const tempDir = os.tmpdir(); + const tempFilePath = path.join(tempDir, 'downloaded-notebook.ipynb'); + fs.writeFileSync(tempFilePath, content); + const uri = vscode.Uri.file(tempFilePath); + await openNotebookDocument(uri); + } catch (error) { + vscode.window.showErrorMessage('Error downloading or opening notebook: ' + error.message); + } +} + +function processNotebookContent(content: string, clusterId: string, region: string): string { + const notebook = JSON.parse(content); + notebook.cells = notebook.cells.map((cell: any) => { + if (cell.metadata && + cell.metadata.jumpStartAlterations && + cell.metadata.jumpStartAlterations.includes('clusterId')) { + cell.source = [ + "%%bash\n", + `aws ssm start-session --target sagemaker-cluster:${clusterId} --region ${region}` + ]; + cell.cell_type = "code"; + } + + if (cell.metadata && + cell.metadata.jumpStartAlterations && + cell.metadata.jumpStartAlterations.includes('clusterName')) { + cell.source = [ + `!hyperpod connect-cluster --cluster-name ${clusterId}` + ] + cell.cell_type = "code"; + } + return cell; + }); + return JSON.stringify(notebook, null, 2); +} + +async function openNotebookDocument(uri: vscode.Uri) { + try { + // Open the notebook document + const document = await vscode.workspace.openNotebookDocument(uri); + // Show the notebook document in a notebook editor + await vscode.window.showNotebookDocument(document); + } catch (error) { + console.error('Failed to open notebook:', error); + vscode.window.showErrorMessage('Failed to open notebook: ' + error.message); + } +} + +function downloadFile(url: string): Promise { + return new Promise((resolve, reject) => { + https.get(url, (response) => { + let data = ''; + response.on('data', (chunk) => { + data += chunk; + }); + response.on('end', () => { + resolve(data); + }); + }).on('error', (error) => { + reject(error); + }); + }); +} +export function deactivate() {} diff --git a/patched-vscode/extensions/sagemaker-open-notebook-extension/tsconfig.json b/patched-vscode/extensions/sagemaker-open-notebook-extension/tsconfig.json new file mode 100644 index 000000000..3e1b41645 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-open-notebook-extension/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "outDir": "./out" + }, + "include": [ + "../sagemaker-open-notebook-extension/src/**/*", + "../../src/vscode-dts/vscode.d.ts" + ] +} diff --git a/patched-vscode/extensions/sagemaker-open-notebook-extension/yarn.lock b/patched-vscode/extensions/sagemaker-open-notebook-extension/yarn.lock new file mode 100644 index 000000000..fb57ccd13 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-open-notebook-extension/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + diff --git a/patched-vscode/extensions/sagemaker-ui-dark-theme/.vscodeignore b/patched-vscode/extensions/sagemaker-ui-dark-theme/.vscodeignore new file mode 100644 index 000000000..56b78554c --- /dev/null +++ b/patched-vscode/extensions/sagemaker-ui-dark-theme/.vscodeignore @@ -0,0 +1,12 @@ +.vscode/** +.vscode-test/** +out/test/** +out/** +test/** +src/** +tsconfig.json +out/test/** +out/** +cgmanifest.json +yarn.lock +preview-src/** diff --git a/patched-vscode/extensions/sagemaker-ui-dark-theme/README.md b/patched-vscode/extensions/sagemaker-ui-dark-theme/README.md new file mode 100644 index 000000000..34c4d69c0 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-ui-dark-theme/README.md @@ -0,0 +1 @@ +# SageMaker UI Dark Theme diff --git a/patched-vscode/extensions/sagemaker-ui-dark-theme/extension-browser.webpack.config.js b/patched-vscode/extensions/sagemaker-ui-dark-theme/extension-browser.webpack.config.js new file mode 100644 index 000000000..68271e0e9 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-ui-dark-theme/extension-browser.webpack.config.js @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright Amazon.com Inc. or its affiliates. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check + +'use strict'; + +const withBrowserDefaults = require('../shared.webpack.config').browser; + +module.exports = withBrowserDefaults({ + context: __dirname, + entry: { + extension: './src/extension.ts' + }, +}); diff --git a/patched-vscode/extensions/sagemaker-ui-dark-theme/extension.webpack.config.js b/patched-vscode/extensions/sagemaker-ui-dark-theme/extension.webpack.config.js new file mode 100644 index 000000000..598526267 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-ui-dark-theme/extension.webpack.config.js @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright Amazon.com Inc. or its affiliates. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check + +'use strict'; + +const withDefaults = require('../shared.webpack.config'); + +module.exports = withDefaults({ + context: __dirname, + resolve: { + mainFields: ['module', 'main'] + }, + entry: { + extension: './src/extension.ts', + } +}); diff --git a/patched-vscode/extensions/sagemaker-ui-dark-theme/package.json b/patched-vscode/extensions/sagemaker-ui-dark-theme/package.json new file mode 100644 index 000000000..2c6c0f068 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-ui-dark-theme/package.json @@ -0,0 +1,46 @@ +{ + "name": "sagemaker-ui-dark-theme", + "displayName": "SageMaker UI Dark Theme", + "description": "SageMaker UI Dark Theme", + "extensionKind": [ + "workspace" + ], + "version": "1.0.0", + "publisher": "sagemaker", + "license": "MIT", + "engines": { + "vscode": "^1.70.0" + }, + "main": "./out/extension", + "categories": [ + "Other" + ], + "activationEvents": [ + "onStartupFinished" + ], + "capabilities": { + "virtualWorkspaces": true, + "untrustedWorkspaces": { + "supported": true + } + }, + "contributes": { + "configuration": { + "type": "object", + "title": "SageMaker UI Dark Theme", + "properties": {} + }, + "commands": [ + ] + }, + "scripts": { + "compile": "gulp compile-extension:sagemaker-ui-dark-theme", + "watch": "npm run build-preview && gulp watch-extension:sagemaker-ui-dark-theme", + "vscode:prepublish": "npm run build-ext", + "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:sagemaker-ui-dark-theme ./tsconfig.json" + }, + "dependencies": { + }, + "repository": { + } +} diff --git a/patched-vscode/extensions/sagemaker-ui-dark-theme/src/extension.ts b/patched-vscode/extensions/sagemaker-ui-dark-theme/src/extension.ts new file mode 100644 index 000000000..8f6395a02 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-ui-dark-theme/src/extension.ts @@ -0,0 +1,51 @@ +import * as vscode from 'vscode'; + +const SERVICE_NAME_ENV_KEY = 'SERVICE_NAME'; +const SERVICE_NAME_ENV_VALUE = 'SageMakerUnifiedStudio'; +const DEFAULT_THEME = 'Default Dark Modern'; + +let outputChannel: vscode.OutputChannel; + +export function activate() { + // Check if in SageMaker Unified Studio + const envValue = process.env[SERVICE_NAME_ENV_KEY]; + if (!envValue || envValue !== SERVICE_NAME_ENV_VALUE) { + return; + } + + const config = vscode.workspace.getConfiguration(); + const themeConfig = config.inspect('workbench.colorTheme'); + outputChannel = vscode.window.createOutputChannel('SageMaker UI Dark Theme'); + + outputChannel.appendLine(`Current theme configuration: ${JSON.stringify(themeConfig, null, 2)}`); + + // Check if theme is only set at default level + if (themeConfig?.globalValue === undefined && + themeConfig?.workspaceValue === undefined && + themeConfig?.workspaceFolderValue === undefined) { + + outputChannel.appendLine('Theme only set at default level, applying theme update'); + + // Update the configuration + Promise.resolve( + config.update('workbench.colorTheme', DEFAULT_THEME, vscode.ConfigurationTarget.Global) + .then(() => { + outputChannel.appendLine(`Theme configuration updated to ${DEFAULT_THEME}`); + // Reload to apply theme + return vscode.commands.executeCommand('workbench.action.reloadWindow'); + }) + .then(() => outputChannel.appendLine('Theme applied successfully')) + ) + .catch((error) => { + outputChannel.appendLine(`Failed to apply theme: ${error}`); + }); + } else { + outputChannel.appendLine('Theme already configured in user or workspace settings, not overriding'); + } +} + +export function deactivate() { + if (outputChannel) { + outputChannel.dispose(); + } +} diff --git a/patched-vscode/extensions/sagemaker-ui-dark-theme/src/test/extension.test.ts b/patched-vscode/extensions/sagemaker-ui-dark-theme/src/test/extension.test.ts new file mode 100644 index 000000000..971197549 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-ui-dark-theme/src/test/extension.test.ts @@ -0,0 +1,123 @@ +import * as assert from 'assert'; +import * as vscode from 'vscode'; + +const DEFAULT_DARK_MODERN = 'Default Dark Modern'; +const DEFAULT_LIGHT_MODERN = 'Default Light Modern'; + +async function waitForThemeChange(expectedTheme: string | undefined, timeoutMs: number): Promise { + const startTime = Date.now(); + + while (Date.now() - startTime < timeoutMs) { + const currentTheme = vscode.workspace.getConfiguration('workbench').inspect('colorTheme'); + + if (currentTheme?.globalValue === expectedTheme) { + return; + } + await new Promise(resolve => setTimeout(resolve, 100)); + } + throw new Error(`Theme did not change to ${expectedTheme} at the global level within ${timeoutMs}ms`); +} + +suite('SageMaker UI Dark Theme Extension Tests - In SageMaker Unified Studio Environment', () => { + // Store original ENV variable value + const originalEnv = process.env.SERVICE_NAME; + + suiteSetup(() => { + // Clear the theme configurations + vscode.workspace.getConfiguration('workbench').update('colorTheme', undefined, vscode.ConfigurationTarget.Global); + vscode.workspace.getConfiguration('workbench').update('colorTheme', undefined, vscode.ConfigurationTarget.Workspace); + + // Set ENV variable value for SageMaker Unified Studio environment + process.env.SERVICE_NAME = 'SageMakerUnifiedStudio'; + }); + + suiteTeardown(() => { + // Clear the theme configurations + vscode.workspace.getConfiguration('workbench').update('colorTheme', undefined, vscode.ConfigurationTarget.Global); + + // Restore ENV variable value to original + originalEnv ? (process.env.SERVICE_NAME = originalEnv) : delete process.env.SERVICE_NAME; + }); + + test('Theme is set when global and workspace theme configurations are unset', async () => { + // Poll for theme update + await waitForThemeChange(DEFAULT_DARK_MODERN, 10000); + + const config = vscode.workspace.getConfiguration(); + const theme = config.inspect('workbench.colorTheme'); + + assert.strictEqual(theme?.globalValue, DEFAULT_DARK_MODERN, `Global theme should be set to ${DEFAULT_DARK_MODERN}`); + }); +}); + +suite('SageMaker UI Dark Theme Extension Tests - In SageMaker Unified Studio Environment', () => { + // Store original ENV variable value + const originalEnv = process.env.SERVICE_NAME; + + suiteSetup(() => { + // Set the global theme configuration to Default Light Modern + vscode.workspace.getConfiguration('workbench').update('colorTheme', DEFAULT_LIGHT_MODERN, vscode.ConfigurationTarget.Global); + vscode.workspace.getConfiguration('workbench').update('colorTheme', undefined, vscode.ConfigurationTarget.Workspace); + + // Set ENV variable value for SageMaker Unified Studio environment + process.env.SERVICE_NAME = 'SageMakerUnifiedStudio'; + }); + + suiteTeardown(() => { + // Clear the theme configurations + vscode.workspace.getConfiguration('workbench').update('colorTheme', undefined, vscode.ConfigurationTarget.Global); + + // Restore ENV variable value to original + originalEnv ? (process.env.SERVICE_NAME = originalEnv) : delete process.env.SERVICE_NAME; + }); + + test('Theme is not set when global theme configuration is set', async () => { + // Poll for theme update + await waitForThemeChange(DEFAULT_LIGHT_MODERN, 10000); + + // Poll for Default Dark Modern theme update (expected to fail) + try { + await waitForThemeChange(DEFAULT_DARK_MODERN, 10000); + assert.fail(`Global theme should be kept as ${DEFAULT_LIGHT_MODERN}`); + } catch (error) { + // Expected behavior: Theme should not be set + } + + const config = vscode.workspace.getConfiguration(); + const theme = config.inspect('workbench.colorTheme'); + + assert.strictEqual(theme?.globalValue, DEFAULT_LIGHT_MODERN, `Global theme should be kept as ${DEFAULT_LIGHT_MODERN}`); + }); +}); + +suite('SageMaker UI Dark Theme Extension Tests - In SageMaker AI Environment', () => { + // Store original ENV variable value + const originalEnv = process.env.SERVICE_NAME; + + suiteSetup(() => { + // Clear the global theme configuration + vscode.workspace.getConfiguration('workbench').update('colorTheme', undefined, vscode.ConfigurationTarget.Global); + vscode.workspace.getConfiguration('workbench').update('colorTheme', undefined, vscode.ConfigurationTarget.Workspace); + + // Ensure ENV variable value for SageMaker Unified Studio environment is NOT set + delete process.env.SERVICE_NAME; + }); + + suiteTeardown(() => { + // Clear the global theme configuration + vscode.workspace.getConfiguration('workbench').update('colorTheme', undefined, vscode.ConfigurationTarget.Global); + + // Restore ENV variable value to original + originalEnv ? (process.env.SERVICE_NAME = originalEnv) : delete process.env.SERVICE_NAME; + }); + + test('Theme is not set', async () => { + // Poll for theme update + await waitForThemeChange(undefined, 10000); + + const config = vscode.workspace.getConfiguration(); + const theme = config.inspect('workbench.colorTheme'); + + assert.strictEqual(theme?.globalValue, undefined, 'Global theme should not be set'); + }); +}); diff --git a/patched-vscode/extensions/sagemaker-ui-dark-theme/src/test/index.ts b/patched-vscode/extensions/sagemaker-ui-dark-theme/src/test/index.ts new file mode 100644 index 000000000..0049ecb76 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-ui-dark-theme/src/test/index.ts @@ -0,0 +1,33 @@ +import * as path from 'path'; +import * as testRunner from '../../../../test/integration/electron/testrunner'; + +const options: import('mocha').MochaOptions = { + ui: 'tdd', + color: true, + timeout: 60000 +}; + +// Set the suite name +let suite = ''; +if (process.env.VSCODE_BROWSER) { + suite = `${process.env.VSCODE_BROWSER} Browser Integration SageMaker UI Dark Theme Tests`; +} else if (process.env.REMOTE_VSCODE) { + suite = 'Remote Integration SageMaker UI Dark Theme Tests'; +} else { + suite = 'Integration SageMaker UI Dark Theme Tests'; +} + +if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) { + options.reporter = 'mocha-multi-reporters'; + options.reporterOptions = { + reporterEnabled: 'spec, mocha-junit-reporter', + mochaJunitReporterReporterOptions: { + testsuitesTitle: `${suite} ${process.platform}`, + mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${process.arch}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`) + } + }; +} + +testRunner.configure(options); + +export = testRunner; diff --git a/patched-vscode/extensions/sagemaker-ui-dark-theme/tsconfig.json b/patched-vscode/extensions/sagemaker-ui-dark-theme/tsconfig.json new file mode 100644 index 000000000..fcd79775d --- /dev/null +++ b/patched-vscode/extensions/sagemaker-ui-dark-theme/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "outDir": "./out" + }, + "include": [ + "src/**/*", + "../../src/vscode-dts/vscode.d.ts" + ] +} diff --git a/patched-vscode/extensions/sagemaker-ui-dark-theme/yarn.lock b/patched-vscode/extensions/sagemaker-ui-dark-theme/yarn.lock new file mode 100644 index 000000000..fb57ccd13 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-ui-dark-theme/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + diff --git a/patched-vscode/extensions/yarn.lock b/patched-vscode/extensions/yarn.lock index fa4595ffa..5be9ecc97 100644 --- a/patched-vscode/extensions/yarn.lock +++ b/patched-vscode/extensions/yarn.lock @@ -127,12 +127,12 @@ node-addon-api "^3.2.1" node-gyp-build "^4.3.0" -braces@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== +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== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" coffeescript@1.12.7: version "1.12.7" @@ -180,10 +180,10 @@ fast-plist@0.1.2: resolved "https://registry.yarnpkg.com/fast-plist/-/fast-plist-0.1.2.tgz#a45aff345196006d406ca6cdcd05f69051ef35b8" integrity sha1-pFr/NFGWAG1AbKbNzQX2kFHvNbg= -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +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== dependencies: to-regex-range "^5.0.1" @@ -205,11 +205,11 @@ is-number@^7.0.0: integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== micromatch@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.2" + braces "^3.0.3" picomatch "^2.3.1" node-addon-api@^3.2.1: diff --git a/patched-vscode/package.json b/patched-vscode/package.json index 804d8a602..2197aa032 100644 --- a/patched-vscode/package.json +++ b/patched-vscode/package.json @@ -27,7 +27,7 @@ "watch-client": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js watch-client", "watch-clientd": "deemon yarn watch-client", "kill-watch-clientd": "deemon --kill yarn watch-client", - "watch-extensions": "node --max-old-space-size=4095 ./node_modules/gulp/bin/gulp.js watch-extensions watch-extension-media", + "watch-extensions": "node --max-old-space-size=8191 ./node_modules/gulp/bin/gulp.js watch-extensions watch-extension-media", "watch-extensionsd": "deemon yarn watch-extensions", "kill-watch-extensionsd": "deemon --kill yarn watch-extensions", "precommit": "node build/hygiene.js", diff --git a/patched-vscode/product.json b/patched-vscode/product.json index 124efc77d..c38cec00a 100644 --- a/patched-vscode/product.json +++ b/patched-vscode/product.json @@ -33,15 +33,6 @@ "webviewContentExternalBaseUrlTemplate": "https://{{uuid}}.vscode-cdn.net/insider/ef65ac1ba57f57f2a3961bfe94aa20481caca4c6/out/vs/workbench/contrib/webview/browser/pre/", "builtInExtensions": [ ], - "extensionsGallery": { - "serviceUrl": "https://open-vsx.org/vscode/gallery", - "itemUrl": "https://open-vsx.org/vscode/item", - "resourceUrlTemplate": "https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}", - "controlUrl": "", - "recommendationsUrl": "", - "nlsBaseUrl": "", - "publisherUrl": "" - }, "linkProtectionTrustedDomains": [ "https://open-vsx.org" ] diff --git a/patched-vscode/remote/yarn.lock b/patched-vscode/remote/yarn.lock index 4241bf03b..43918876c 100644 --- a/patched-vscode/remote/yarn.lock +++ b/patched-vscode/remote/yarn.lock @@ -185,12 +185,12 @@ bl@^4.0.3: inherits "^2.0.4" readable-stream "^3.4.0" -braces@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== +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== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" buffer-crc32@~0.2.3: version "0.2.13" @@ -263,10 +263,10 @@ file-uri-to-path@1.0.0: resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +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== dependencies: to-regex-range "^5.0.1" @@ -378,11 +378,11 @@ lru-cache@^6.0.0: yallist "^4.0.0" micromatch@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.2" + braces "^3.0.3" picomatch "^2.3.1" mimic-response@^3.1.0: @@ -587,9 +587,9 @@ strip-json-comments@~2.0.1: integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= tar-fs@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" - integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== + version "2.1.3" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.3.tgz#fb3b8843a26b6f13a08e606f7922875eb1fbbf92" + integrity sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg== dependencies: chownr "^1.1.1" mkdirp-classic "^0.5.2" diff --git a/patched-vscode/src/tsconfig.json b/patched-vscode/src/tsconfig.json index 2ac819490..09d0aff63 100644 --- a/patched-vscode/src/tsconfig.json +++ b/patched-vscode/src/tsconfig.json @@ -38,4 +38,4 @@ "vscode-dts/vscode.proposed.*.d.ts", "vscode-dts/vscode.d.ts" ] -} +} \ No newline at end of file diff --git a/patched-vscode/src/vs/base/common/platform.ts b/patched-vscode/src/vs/base/common/platform.ts index 2251c7db5..8aeccf327 100644 --- a/patched-vscode/src/vs/base/common/platform.ts +++ b/patched-vscode/src/vs/base/common/platform.ts @@ -2,8 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; - export const LANGUAGE_DEFAULT = 'en'; let _isWindows = false; @@ -93,7 +91,6 @@ if (typeof nodeProcess === 'object') { const resolved = nlsConfig.availableLanguages['*']; _locale = nlsConfig.locale; _platformLocale = nlsConfig.osLocale; - // VSCode's default language is 'en' _language = resolved ? resolved : LANGUAGE_DEFAULT; _translationsConfigFile = nlsConfig._translationsConfigFile; } catch (e) { @@ -111,18 +108,21 @@ else if (typeof navigator === 'object' && !isElectronRenderer) { _isLinux = _userAgent.indexOf('Linux') >= 0; _isMobile = _userAgent?.indexOf('Mobi') >= 0; _isWeb = true; - - const configuredLocale = nls.getConfiguredDefaultLocale( - // This call _must_ be done in the file that calls `nls.getConfiguredDefaultLocale` - // to ensure that the NLS AMD Loader plugin has been loaded and configured. - // This is because the loader plugin decides what the default locale is based on - // how it's able to resolve the strings. - nls.localize({ key: 'ensureLoaderPluginIsLoaded', comment: ['{Locked}'] }, '_') - ); - - _locale = configuredLocale || LANGUAGE_DEFAULT; - _language = _locale; - _platformLocale = navigator.language; + _locale = LANGUAGE_DEFAULT; + _language = _locale; + _platformLocale = navigator.language; + const el = typeof document !== 'undefined' && document.getElementById('vscode-remote-nls-configuration'); + const rawNlsConfig = el && el.getAttribute('data-settings'); + if (rawNlsConfig) { + try { + const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig); + const resolved = nlsConfig.availableLanguages['*']; + _locale = nlsConfig.locale; + _platformLocale = nlsConfig.osLocale; + _language = resolved ? resolved : LANGUAGE_DEFAULT; + _translationsConfigFile = nlsConfig._translationsConfigFile; + } catch (error) { /* Oh well. */ } + } } // Unknown environment @@ -178,7 +178,7 @@ export const userAgent = _userAgent; /** * The language used for the user interface. The format of * the string is all lower case (e.g. zh-tw for Traditional - * Chinese) + * Chinese or de for German) */ export const language = _language; @@ -204,15 +204,16 @@ export namespace Language { } /** - * The OS locale or the locale specified by --locale. The format of - * the string is all lower case (e.g. zh-tw for Traditional - * Chinese). The UI is not necessarily shown in the provided locale. + * Desktop: The OS locale or the locale specified by --locale or `argv.json`. + * Web: matches `platformLocale`. + * + * The UI is not necessarily shown in the provided locale. */ export const locale = _locale; /** * This will always be set to the OS/browser's locale regardless of - * what was specified by --locale. The format of the string is all + * what was specified otherwise. The format of the string is all * lower case (e.g. zh-tw for Traditional Chinese). The UI is not * necessarily shown in the provided locale. */ diff --git a/patched-vscode/src/vs/code/browser/workbench/workbench.html b/patched-vscode/src/vs/code/browser/workbench/workbench.html index cfee57cc1..309e0980b 100644 --- a/patched-vscode/src/vs/code/browser/workbench/workbench.html +++ b/patched-vscode/src/vs/code/browser/workbench/workbench.html @@ -19,13 +19,16 @@ + + + - + @@ -33,27 +36,35 @@ - - - - - + + + + + diff --git a/patched-vscode/src/vs/platform/environment/common/environmentService.ts b/patched-vscode/src/vs/platform/environment/common/environmentService.ts index cd55aa9b2..180599bbd 100644 --- a/patched-vscode/src/vs/platform/environment/common/environmentService.ts +++ b/patched-vscode/src/vs/platform/environment/common/environmentService.ts @@ -3,16 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { toLocalISOString } from 'vs/base/common/date'; -import { memoize } from 'vs/base/common/decorators'; -import { FileAccess, Schemas } from 'vs/base/common/network'; -import { dirname, join, normalize, resolve } from 'vs/base/common/path'; -import { env } from 'vs/base/common/process'; -import { joinPath } from 'vs/base/common/resources'; -import { URI } from 'vs/base/common/uri'; -import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; -import { ExtensionKind, IExtensionHostDebugParams, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IProductService } from 'vs/platform/product/common/productService'; +import { toLocalISOString } from '../../../base/common/date.js'; +import { memoize } from '../../../base/common/decorators.js'; +import { FileAccess, Schemas } from '../../../base/common/network.js'; +import { dirname, join, normalize, resolve } from '../../../base/common/path.js'; +import { env } from '../../../base/common/process.js'; +import { joinPath } from '../../../base/common/resources.js'; +import { URI } from '../../../base/common/uri.js'; +import { NativeParsedArgs } from './argv.js'; +import { ExtensionKind, IExtensionHostDebugParams, INativeEnvironmentService } from './environment.js'; +import { IProductService } from '../../product/common/productService.js'; export const EXTENSION_IDENTIFIER_WITH_LOG_REGEX = /^([^.]+\..+)[:=](.+)$/; @@ -101,7 +101,7 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron return URI.file(join(vscodePortable, 'argv.json')); } - return joinPath(this.userHome, this.productService.dataFolderName, 'argv.json'); + return joinPath(this.appSettingsHome, 'argv.json'); } @memoize @@ -252,7 +252,7 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron return undefined; } - editSessionId: string | undefined = this.args['editSessionId']; + get editSessionId(): string | undefined { return this.args['editSessionId']; } get continueOn(): string | undefined { return this.args['continueOn']; diff --git a/patched-vscode/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/patched-vscode/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 275d5c3c5..5fba2afb5 100644 --- a/patched-vscode/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/patched-vscode/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -3,62 +3,70 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Promises, Queue } from 'vs/base/common/async'; -import { VSBuffer } from 'vs/base/common/buffer'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { IStringDictionary } from 'vs/base/common/collections'; -import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { CancellationError, getErrorMessage } from 'vs/base/common/errors'; -import { Emitter } from 'vs/base/common/event'; -import { hash } from 'vs/base/common/hash'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { ResourceSet } from 'vs/base/common/map'; -import { Schemas } from 'vs/base/common/network'; -import * as path from 'vs/base/common/path'; -import { joinPath } from 'vs/base/common/resources'; -import * as semver from 'vs/base/common/semver/semver'; -import { isBoolean } from 'vs/base/common/types'; -import { URI } from 'vs/base/common/uri'; -import { generateUuid } from 'vs/base/common/uuid'; -import * as pfs from 'vs/base/node/pfs'; -import { extract, IFile, zip } from 'vs/base/node/zip'; -import * as nls from 'vs/nls'; -import { IDownloadService } from 'vs/platform/download/common/download'; -import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; -import { AbstractExtensionManagementService, AbstractExtensionTask, ExtensionVerificationStatus, IInstallExtensionTask, InstallExtensionTaskOptions, IUninstallExtensionTask, toExtensionManagementError, UninstallExtensionTaskOptions } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService'; +import * as fs from 'fs'; +import { Promises, Queue } from '../../../base/common/async.js'; +import { VSBuffer } from '../../../base/common/buffer.js'; +import { CancellationToken } from '../../../base/common/cancellation.js'; +import { IStringDictionary } from '../../../base/common/collections.js'; +import { CancellationError, getErrorMessage } from '../../../base/common/errors.js'; +import { Emitter } from '../../../base/common/event.js'; +import { hash } from '../../../base/common/hash.js'; +import { Disposable } from '../../../base/common/lifecycle.js'; +import { ResourceMap, ResourceSet } from '../../../base/common/map.js'; +import { Schemas } from '../../../base/common/network.js'; +import * as path from '../../../base/common/path.js'; +import { joinPath } from '../../../base/common/resources.js'; +import * as semver from '../../../base/common/semver/semver.js'; +import { isBoolean, isDefined, isUndefined } from '../../../base/common/types.js'; +import { URI } from '../../../base/common/uri.js'; +import { generateUuid } from '../../../base/common/uuid.js'; +import * as pfs from '../../../base/node/pfs.js'; +import { extract, IFile, zip } from '../../../base/node/zip.js'; +import * as nls from '../../../nls.js'; +import { IDownloadService } from '../../download/common/download.js'; +import { INativeEnvironmentService } from '../../environment/common/environment.js'; +import { AbstractExtensionManagementService, AbstractExtensionTask, IInstallExtensionTask, InstallExtensionTaskOptions, IUninstallExtensionTask, toExtensionManagementError, UninstallExtensionTaskOptions } from '../common/abstractExtensionManagementService.js'; import { ExtensionManagementError, ExtensionManagementErrorCode, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService, IGalleryExtension, ILocalExtension, InstallOperation, Metadata, InstallOptions, IProductVersion, EXTENSION_INSTALL_CLIENT_TARGET_PLATFORM_CONTEXT, -} from 'vs/platform/extensionManagement/common/extensionManagement'; -import { areSameExtensions, computeTargetPlatform, ExtensionKey, getGalleryExtensionId, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { IExtensionsProfileScannerService, IScannedProfileExtension } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; -import { IExtensionsScannerService, IScannedExtension, ScanOptions } from 'vs/platform/extensionManagement/common/extensionsScannerService'; -import { ExtensionsDownloader } from 'vs/platform/extensionManagement/node/extensionDownloader'; -import { ExtensionsLifecycle } from 'vs/platform/extensionManagement/node/extensionLifecycle'; -import { fromExtractError, getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil'; -import { ExtensionsManifestCache } from 'vs/platform/extensionManagement/node/extensionsManifestCache'; -import { DidChangeProfileExtensionsEvent, ExtensionsWatcher } from 'vs/platform/extensionManagement/node/extensionsWatcher'; -import { ExtensionType, IExtension, IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions'; -import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator'; -import { FileChangesEvent, FileChangeType, FileOperationResult, IFileService, toFileOperationResult } from 'vs/platform/files/common/files'; -import { IInstantiationService, refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { ILogService } from 'vs/platform/log/common/log'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; + ExtensionSignatureVerificationCode, + computeSize, + IAllowedExtensionsService, + // @ts-expect-error no-unused-variable + VerifyExtensionSignatureConfigKey, + shouldRequireRepositorySignatureFor, +} from '../common/extensionManagement.js'; +import { areSameExtensions, computeTargetPlatform, ExtensionKey, getGalleryExtensionId, groupByExtension } from '../common/extensionManagementUtil.js'; +import { IExtensionsProfileScannerService, IScannedProfileExtension } from '../common/extensionsProfileScannerService.js'; +import { IExtensionsScannerService, IScannedExtension, ManifestMetadata, UserExtensionsScanOptions } from '../common/extensionsScannerService.js'; +import { ExtensionsDownloader } from './extensionDownloader.js'; +import { ExtensionsLifecycle } from './extensionLifecycle.js'; +import { fromExtractError, getManifest } from './extensionManagementUtil.js'; +import { ExtensionsManifestCache } from './extensionsManifestCache.js'; +import { DidChangeProfileExtensionsEvent, ExtensionsWatcher } from './extensionsWatcher.js'; +import { ExtensionType, IExtension, IExtensionManifest, TargetPlatform } from '../../extensions/common/extensions.js'; +import { isEngineValid } from '../../extensions/common/extensionValidator.js'; +import { FileChangesEvent, FileChangeType, FileOperationResult, IFileService, IFileStat, toFileOperationResult } from '../../files/common/files.js'; +import { IInstantiationService, refineServiceDecorator } from '../../instantiation/common/instantiation.js'; +import { ILogService } from '../../log/common/log.js'; +import { IProductService } from '../../product/common/productService.js'; +import { ITelemetryService } from '../../telemetry/common/telemetry.js'; +import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js'; +import { IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.js'; +import { IConfigurationService } from '../../configuration/common/configuration.js'; +import { IExtensionGalleryManifestService } from '../common/extensionGalleryManifest.js'; export const INativeServerExtensionManagementService = refineServiceDecorator(IExtensionManagementService); export interface INativeServerExtensionManagementService extends IExtensionManagementService { readonly _serviceBrand: undefined; scanAllUserInstalledExtensions(): Promise; scanInstalledExtensionAtLocation(location: URI): Promise; - markAsUninstalled(...extensions: IExtension[]): Promise; + deleteExtensions(...extensions: IExtension[]): Promise; } -type ExtractExtensionResult = { readonly local: ILocalExtension; readonly verificationStatus?: ExtensionVerificationStatus }; +type ExtractExtensionResult = { readonly local: ILocalExtension; readonly verificationStatus?: ExtensionSignatureVerificationCode }; const DELETED_FOLDER_POSTFIX = '.vsctmp'; @@ -74,17 +82,21 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi @IExtensionGalleryService galleryService: IExtensionGalleryService, @ITelemetryService telemetryService: ITelemetryService, @ILogService logService: ILogService, - @INativeEnvironmentService environmentService: INativeEnvironmentService, + @INativeEnvironmentService private readonly environmentService: INativeEnvironmentService, @IExtensionsScannerService private readonly extensionsScannerService: IExtensionsScannerService, @IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService, @IDownloadService private downloadService: IDownloadService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IFileService private readonly fileService: IFileService, + // @ts-expect-error no-unused-variable + @IConfigurationService private readonly configurationService: IConfigurationService, + @IExtensionGalleryManifestService protected readonly extensionGalleryManifestService: IExtensionGalleryManifestService, @IProductService productService: IProductService, + @IAllowedExtensionsService allowedExtensionsService: IAllowedExtensionsService, @IUriIdentityService uriIdentityService: IUriIdentityService, @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService ) { - super(galleryService, telemetryService, uriIdentityService, logService, productService, userDataProfilesService); + super(galleryService, telemetryService, uriIdentityService, logService, productService, allowedExtensionsService, userDataProfilesService); const extensionLifecycle = this._register(instantiationService.createInstance(ExtensionsLifecycle)); this.extensionsScanner = this._register(instantiationService.createInstance(ExtensionsScanner, extension => extensionLifecycle.postUninstall(extension))); this.manifestCache = this._register(new ExtensionsManifestCache(userDataProfilesService, fileService, uriIdentityService, this, this.logService)); @@ -110,12 +122,6 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi return URI.file(location); } - async unzip(zipLocation: URI): Promise { - this.logService.trace('ExtensionManagementService#unzip', zipLocation.toString()); - const local = await this.install(zipLocation); - return local.identifier; - } - async getManifest(vsix: URI): Promise { const { location, cleanup } = await this.downloadVsix(vsix); const zipPath = path.resolve(location.fsPath); @@ -131,7 +137,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi } scanAllUserInstalledExtensions(): Promise { - return this.extensionsScanner.scanAllUserExtensions(false); + return this.extensionsScanner.scanAllUserExtensions(); } scanInstalledExtensionAtLocation(location: URI): Promise { @@ -150,6 +156,11 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi throw new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", extensionId, this.productService.version)); } + const allowedToInstall = this.allowedExtensionsService.isAllowed({ id: extensionId, version: manifest.version, publisherDisplayName: undefined }); + if (allowedToInstall !== true) { + throw new Error(nls.localize('notAllowed', "This extension cannot be installed because {0}", allowedToInstall.value)); + } + const results = await this.installExtensions([{ manifest, extension: location, options }]); const result = results.find(({ identifier }) => areSameExtensions(identifier, { id: extensionId })); if (result?.local) { @@ -186,7 +197,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi return extensionsToInstall; } - async updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation: URI = this.userDataProfilesService.defaultProfile.extensionsResource): Promise { + async updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation: URI): Promise { this.logService.trace('ExtensionManagementService#updateMetadata', local.identifier.id); if (metadata.isPreReleaseVersion) { metadata.preRelease = true; @@ -204,41 +215,32 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi } local = await this.extensionsScanner.updateMetadata(local, metadata, profileLocation); this.manifestCache.invalidate(profileLocation); - this._onDidUpdateExtensionMetadata.fire(local); + this._onDidUpdateExtensionMetadata.fire({ local, profileLocation }); return local; } - async reinstallFromGallery(extension: ILocalExtension): Promise { - this.logService.trace('ExtensionManagementService#reinstallFromGallery', extension.identifier.id); - if (!this.galleryService.isEnabled()) { - throw new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled")); - } - - const targetPlatform = await this.getTargetPlatform(); - const [galleryExtension] = await this.galleryService.getExtensions([{ ...extension.identifier, preRelease: extension.preRelease }], { targetPlatform, compatible: true }, CancellationToken.None); - if (!galleryExtension) { - throw new Error(nls.localize('Not a Marketplace extension', "Only Marketplace Extensions can be reinstalled")); - } - - await this.extensionsScanner.setUninstalled(extension); - try { - await this.extensionsScanner.removeUninstalledExtension(extension); - } catch (e) { - throw new Error(nls.localize('removeError', "Error while removing the extension: {0}. Please Quit and Start VS Code before trying again.", toErrorMessage(e))); - } - return this.installFromGallery(galleryExtension); + protected deleteExtension(extension: ILocalExtension): Promise { + return this.extensionsScanner.deleteExtension(extension, 'remove'); } protected copyExtension(extension: ILocalExtension, fromProfileLocation: URI, toProfileLocation: URI, metadata: Partial): Promise { return this.extensionsScanner.copyExtension(extension, fromProfileLocation, toProfileLocation, metadata); } + protected moveExtension(extension: ILocalExtension, fromProfileLocation: URI, toProfileLocation: URI, metadata: Partial): Promise { + return this.extensionsScanner.moveExtension(extension, fromProfileLocation, toProfileLocation, metadata); + } + + protected removeExtension(extension: ILocalExtension, fromProfileLocation: URI): Promise { + return this.extensionsScanner.removeExtension(extension.identifier, fromProfileLocation); + } + copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise { return this.extensionsScanner.copyExtensions(fromProfileLocation, toProfileLocation, { version: this.productService.version, date: this.productService.date }); } - markAsUninstalled(...extensions: IExtension[]): Promise { - return this.extensionsScanner.setUninstalled(...extensions); + deleteExtensions(...extensions: IExtension[]): Promise { + return this.extensionsScanner.setExtensionsForRemoval(...extensions); } async cleanUp(): Promise { @@ -251,7 +253,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi } async download(extension: IGalleryExtension, operation: InstallOperation, donotVerifySignature: boolean): Promise { - const { location } = await this.extensionsDownloader.download(extension, operation, !donotVerifySignature); + const { location } = await this.downloadExtension(extension, operation, !donotVerifySignature); return location; } @@ -293,11 +295,11 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi } protected createUninstallExtensionTask(extension: ILocalExtension, options: UninstallExtensionTaskOptions): IUninstallExtensionTask { - return new UninstallExtensionInProfileTask(extension, options.profileLocation, this.extensionsProfileScannerService); + return new UninstallExtensionInProfileTask(extension, options, this.extensionsProfileScannerService); } private async downloadAndExtractGalleryExtension(extensionKey: ExtensionKey, gallery: IGalleryExtension, operation: InstallOperation, options: InstallExtensionTaskOptions, token: CancellationToken): Promise { - const { verificationStatus, location } = await this.extensionsDownloader.download(gallery, operation, !options.donotVerifySignature, options.context?.[EXTENSION_INSTALL_CLIENT_TARGET_PLATFORM_CONTEXT]); + const { verificationStatus, location } = await this.downloadExtension(gallery, operation, !options.donotVerifySignature, options.context?.[EXTENSION_INSTALL_CLIENT_TARGET_PLATFORM_CONTEXT]); try { if (token.isCancellationRequested) { @@ -305,30 +307,26 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi } // validate manifest - await getManifest(location.fsPath); + const manifest = await getManifest(location.fsPath); + if (!new ExtensionKey(gallery.identifier, gallery.version).equals(new ExtensionKey({ id: getGalleryExtensionId(manifest.publisher, manifest.name) }, manifest.version))) { + throw new ExtensionManagementError(nls.localize('invalidManifest', "Cannot install '{0}' extension because of manifest mismatch with Marketplace", gallery.identifier.id), ExtensionManagementErrorCode.Invalid); + } const local = await this.extensionsScanner.extractUserExtension( extensionKey, location.fsPath, - { - id: gallery.identifier.uuid, - publisherId: gallery.publisherId, - publisherDisplayName: gallery.publisherDisplayName, - targetPlatform: gallery.properties.targetPlatform, - isApplicationScoped: options.isApplicationScoped, - isMachineScoped: options.isMachineScoped, - isBuiltin: options.isBuiltin, - isPreReleaseVersion: gallery.properties.isPreReleaseVersion, - hasPreReleaseVersion: gallery.properties.isPreReleaseVersion, - installedTimestamp: Date.now(), - pinned: options.installGivenVersion ? true : !!options.pinned, - preRelease: isBoolean(options.preRelease) - ? options.preRelease - : options.installPreReleaseVersion || gallery.properties.isPreReleaseVersion, - source: 'gallery', - }, false, token); + + if (verificationStatus !== ExtensionSignatureVerificationCode.Success && this.environmentService.isBuilt) { + try { + await this.extensionsDownloader.delete(location); + } catch (e) { + /* Ignore */ + this.logService.warn(`Error while deleting the downloaded file`, location.toString(), getErrorMessage(e)); + } + } + return { local, verificationStatus }; } catch (error) { try { @@ -341,19 +339,57 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi } } + private async downloadExtension(extension: IGalleryExtension, operation: InstallOperation, verifySignature: boolean, clientTargetPlatform?: TargetPlatform): Promise<{ readonly location: URI; readonly verificationStatus: ExtensionSignatureVerificationCode | undefined }> { + if (verifySignature) { + verifySignature = false; + } + const { location, verificationStatus } = await this.extensionsDownloader.download(extension, operation, verifySignature, clientTargetPlatform); + const shouldRequireSignature = shouldRequireRepositorySignatureFor(extension.private, await this.extensionGalleryManifestService.getExtensionGalleryManifest()); + + if ( + verificationStatus !== ExtensionSignatureVerificationCode.Success + && !(verificationStatus === ExtensionSignatureVerificationCode.NotSigned && !shouldRequireSignature) + && verifySignature + && this.environmentService.isBuilt + && (await this.getTargetPlatform()) !== TargetPlatform.LINUX_ARMHF + ) { + try { + await this.extensionsDownloader.delete(location); + } catch (e) { + /* Ignore */ + this.logService.warn(`Error while deleting the downloaded file`, location.toString(), getErrorMessage(e)); + } + + if (!verificationStatus) { + throw new ExtensionManagementError(nls.localize('signature verification not executed', "Signature verification was not executed."), ExtensionManagementErrorCode.SignatureVerificationInternal); + } + + switch (verificationStatus) { + case ExtensionSignatureVerificationCode.PackageIntegrityCheckFailed: + case ExtensionSignatureVerificationCode.SignatureIsInvalid: + case ExtensionSignatureVerificationCode.SignatureManifestIsInvalid: + case ExtensionSignatureVerificationCode.SignatureIntegrityCheckFailed: + case ExtensionSignatureVerificationCode.EntryIsMissing: + case ExtensionSignatureVerificationCode.EntryIsTampered: + case ExtensionSignatureVerificationCode.Untrusted: + case ExtensionSignatureVerificationCode.CertificateRevoked: + case ExtensionSignatureVerificationCode.SignatureIsNotValid: + case ExtensionSignatureVerificationCode.SignatureArchiveHasTooManyEntries: + case ExtensionSignatureVerificationCode.NotSigned: + throw new ExtensionManagementError(nls.localize('signature verification failed', "Signature verification failed with '{0}' error.", verificationStatus), ExtensionManagementErrorCode.SignatureVerificationFailed); + } + + throw new ExtensionManagementError(nls.localize('signature verification failed', "Signature verification failed with '{0}' error.", verificationStatus), ExtensionManagementErrorCode.SignatureVerificationInternal); + } + + return { location, verificationStatus }; + } + private async extractVSIX(extensionKey: ExtensionKey, location: URI, options: InstallExtensionTaskOptions, token: CancellationToken): Promise { const local = await this.extensionsScanner.extractUserExtension( extensionKey, path.resolve(location.fsPath), - { - isApplicationScoped: options.isApplicationScoped, - isMachineScoped: options.isMachineScoped, - isBuiltin: options.isBuiltin, - installedTimestamp: Date.now(), - pinned: options.installGivenVersion ? true : !!options.pinned, - source: 'vsix', - }, - true, + isBoolean(options.keepExisting) ? !options.keepExisting : true, token); return { local }; } @@ -363,7 +399,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi const collectFilesFromDirectory = async (dir: string): Promise => { let entries = await pfs.Promises.readdir(dir); entries = entries.map(e => path.join(dir, e)); - const stats = await Promise.all(entries.map(e => pfs.Promises.stat(e))); + const stats = await Promise.all(entries.map(e => fs.promises.stat(e))); let promise: Promise = Promise.resolve([]); stats.forEach((stat, index) => { const entry = entries[index]; @@ -380,7 +416,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi }; const files = await collectFilesFromDirectory(extension.location.fsPath); - return files.map(f => ({ path: `extension/${path.relative(extension.location.fsPath, f)}`, localPath: f })); + return files.map(f => ({ path: `extension/${path.relative(extension.location.fsPath, f)}`, localPath: f })); } private async onDidChangeExtensionsFromAnotherSource({ added, removed }: DidChangeProfileExtensionsEvent): Promise { @@ -443,8 +479,20 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi continue; } - // Check if this is a directory - if (!(await this.fileService.stat(resource)).isDirectory) { + // Ignore changes to the deleted folder + if (this.uriIdentityService.extUri.basename(resource).endsWith(DELETED_FOLDER_POSTFIX)) { + continue; + } + + try { + // Check if this is a directory + if (!(await this.fileService.stat(resource)).isDirectory) { + continue; + } + } catch (error) { + if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) { + this.logService.error(error); + } continue; } @@ -465,59 +513,77 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi private async addExtensionsToProfile(extensions: [ILocalExtension, Metadata | undefined][], profileLocation: URI): Promise { const localExtensions = extensions.map(e => e[0]); - await this.setInstalled(localExtensions); + await this.extensionsScanner.unsetExtensionsForRemoval(...localExtensions.map(extension => ExtensionKey.create(extension))); await this.extensionsProfileScannerService.addExtensionsToProfile(extensions, profileLocation); this._onDidInstallExtensions.fire(localExtensions.map(local => ({ local, identifier: local.identifier, operation: InstallOperation.None, profileLocation }))); } - - private async setInstalled(extensions: ILocalExtension[]): Promise { - const uninstalled = await this.extensionsScanner.getUninstalledExtensions(); - for (const extension of extensions) { - const extensionKey = ExtensionKey.create(extension); - if (!uninstalled[extensionKey.toString()]) { - continue; - } - this.logService.trace('Removing the extension from uninstalled list:', extensionKey.id); - await this.extensionsScanner.setInstalled(extensionKey); - this.logService.info('Removed the extension from uninstalled list:', extensionKey.id); - } - } } +type UpdateMetadataErrorClassification = { + owner: 'sandy081'; + comment: 'Update metadata error'; + extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'extension identifier' }; + code?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'error code' }; + isProfile?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Is writing into profile' }; +}; +type UpdateMetadataErrorEvent = { + extensionId: string; + code?: string; + isProfile?: boolean; +}; + export class ExtensionsScanner extends Disposable { - private readonly uninstalledResource: URI; - private readonly uninstalledFileLimiter: Queue; + private readonly obsoletedResource: URI; + private readonly obsoleteFileLimiter: Queue; private readonly _onExtract = this._register(new Emitter()); readonly onExtract = this._onExtract.event; + private scanAllExtensionPromise = new ResourceMap>(); + private scanUserExtensionsPromise = new ResourceMap>(); + constructor( private readonly beforeRemovingExtension: (e: ILocalExtension) => Promise, @IFileService private readonly fileService: IFileService, @IExtensionsScannerService private readonly extensionsScannerService: IExtensionsScannerService, @IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @ITelemetryService private readonly telemetryService: ITelemetryService, @ILogService private readonly logService: ILogService, ) { super(); - this.uninstalledResource = joinPath(this.extensionsScannerService.userExtensionsLocation, '.obsolete'); - this.uninstalledFileLimiter = new Queue(); + this.obsoletedResource = joinPath(this.extensionsScannerService.userExtensionsLocation, '.obsolete'); + this.obsoleteFileLimiter = new Queue(); } async cleanUp(): Promise { await this.removeTemporarilyDeletedFolders(); - await this.removeUninstalledExtensions(); + await this.deleteExtensionsMarkedForRemoval(); + //TODO: Remove this initiialization after coupe of releases + await this.initializeExtensionSize(); } async scanExtensions(type: ExtensionType | null, profileLocation: URI, productVersion: IProductVersion): Promise { try { - const userScanOptions: ScanOptions = { includeInvalid: true, profileLocation, productVersion }; + const userScanOptions: UserExtensionsScanOptions = { includeInvalid: true, profileLocation, productVersion }; let scannedExtensions: IScannedExtension[] = []; if (type === null || type === ExtensionType.System) { - scannedExtensions.push(...await this.extensionsScannerService.scanAllExtensions({ includeInvalid: true }, userScanOptions, false)); + let scanAllExtensionsPromise = this.scanAllExtensionPromise.get(profileLocation); + if (!scanAllExtensionsPromise) { + scanAllExtensionsPromise = this.extensionsScannerService.scanAllExtensions({}, userScanOptions) + .finally(() => this.scanAllExtensionPromise.delete(profileLocation)); + this.scanAllExtensionPromise.set(profileLocation, scanAllExtensionsPromise); + } + scannedExtensions.push(...await scanAllExtensionsPromise); } else if (type === ExtensionType.User) { - scannedExtensions.push(...await this.extensionsScannerService.scanUserExtensions(userScanOptions)); + let scanUserExtensionsPromise = this.scanUserExtensionsPromise.get(profileLocation); + if (!scanUserExtensionsPromise) { + scanUserExtensionsPromise = this.extensionsScannerService.scanUserExtensions(userScanOptions) + .finally(() => this.scanUserExtensionsPromise.delete(profileLocation)); + this.scanUserExtensionsPromise.set(profileLocation, scanUserExtensionsPromise); + } + scannedExtensions.push(...await scanUserExtensionsPromise); } scannedExtensions = type !== null ? scannedExtensions.filter(r => r.type === type) : scannedExtensions; return await Promise.all(scannedExtensions.map(extension => this.toLocalExtension(extension))); @@ -526,9 +592,9 @@ export class ExtensionsScanner extends Disposable { } } - async scanAllUserExtensions(excludeOutdated: boolean): Promise { + async scanAllUserExtensions(): Promise { try { - const scannedExtensions = await this.extensionsScannerService.scanUserExtensions({ includeAllVersions: !excludeOutdated, includeInvalid: true }); + const scannedExtensions = await this.extensionsScannerService.scanAllUserExtensions(); return await Promise.all(scannedExtensions.map(extension => this.toLocalExtension(extension))); } catch (error) { throw toExtensionManagementError(error, ExtensionManagementErrorCode.Scanning); @@ -547,86 +613,88 @@ export class ExtensionsScanner extends Disposable { return null; } - async extractUserExtension(extensionKey: ExtensionKey, zipPath: string, metadata: Metadata, removeIfExists: boolean, token: CancellationToken): Promise { + async extractUserExtension(extensionKey: ExtensionKey, zipPath: string, removeIfExists: boolean, token: CancellationToken): Promise { const folderName = extensionKey.toString(); const tempLocation = URI.file(path.join(this.extensionsScannerService.userExtensionsLocation.fsPath, `.${generateUuid()}`)); const extensionLocation = URI.file(path.join(this.extensionsScannerService.userExtensionsLocation.fsPath, folderName)); - let exists = await this.fileService.exists(extensionLocation); + if (await this.fileService.exists(extensionLocation)) { + if (!removeIfExists) { + try { + return await this.scanLocalExtension(extensionLocation, ExtensionType.User); + } catch (error) { + this.logService.warn(`Error while scanning the existing extension at ${extensionLocation.path}. Deleting the existing extension and extracting it.`, getErrorMessage(error)); + } + } - if (exists && removeIfExists) { try { await this.deleteExtensionFromLocation(extensionKey.id, extensionLocation, 'removeExisting'); } catch (error) { throw new ExtensionManagementError(nls.localize('errorDeleting', "Unable to delete the existing folder '{0}' while installing the extension '{1}'. Please delete the folder manually and try again", extensionLocation.fsPath, extensionKey.id), ExtensionManagementErrorCode.Delete); } - exists = false; } - if (exists) { - try { - await this.extensionsScannerService.updateMetadata(extensionLocation, metadata); - } catch (error) { - throw toExtensionManagementError(error, ExtensionManagementErrorCode.UpdateMetadata); + try { + if (token.isCancellationRequested) { + throw new CancellationError(); } - } else { + + // Extract try { - if (token.isCancellationRequested) { - throw new CancellationError(); - } + this.logService.trace(`Started extracting the extension from ${zipPath} to ${extensionLocation.fsPath}`); + await extract(zipPath, tempLocation.fsPath, { sourcePath: 'extension', overwrite: true }, token); + this.logService.info(`Extracted extension to ${extensionLocation}:`, extensionKey.id); + } catch (e) { + throw fromExtractError(e); + } - // Extract - try { - this.logService.trace(`Started extracting the extension from ${zipPath} to ${extensionLocation.fsPath}`); - await extract(zipPath, tempLocation.fsPath, { sourcePath: 'extension', overwrite: true }, token); - this.logService.info(`Extracted extension to ${extensionLocation}:`, extensionKey.id); - } catch (e) { - throw fromExtractError(e); - } + const metadata: ManifestMetadata = { installedTimestamp: Date.now(), targetPlatform: extensionKey.targetPlatform }; + try { + metadata.size = await computeSize(tempLocation, this.fileService); + } catch (error) { + // Log & ignore + this.logService.warn(`Error while getting the size of the extracted extension : ${tempLocation.fsPath}`, getErrorMessage(error)); + } - try { - await this.extensionsScannerService.updateMetadata(tempLocation, metadata); - } catch (error) { - throw toExtensionManagementError(error, ExtensionManagementErrorCode.UpdateMetadata); - } + try { + await this.extensionsScannerService.updateManifestMetadata(tempLocation, metadata); + } catch (error) { + this.telemetryService.publicLog2('extension:extract', { extensionId: extensionKey.id, code: `${toFileOperationResult(error)}` }); + throw toExtensionManagementError(error, ExtensionManagementErrorCode.UpdateMetadata); + } - if (token.isCancellationRequested) { - throw new CancellationError(); - } + if (token.isCancellationRequested) { + throw new CancellationError(); + } - // Rename - try { - this.logService.trace(`Started renaming the extension from ${tempLocation.fsPath} to ${extensionLocation.fsPath}`); - await this.rename(tempLocation.fsPath, extensionLocation.fsPath); - this.logService.info('Renamed to', extensionLocation.fsPath); - } catch (error) { - if (error.code === 'ENOTEMPTY') { - this.logService.info(`Rename failed because extension was installed by another source. So ignoring renaming.`, extensionKey.id); - try { await this.fileService.del(tempLocation, { recursive: true }); } catch (e) { /* ignore */ } - } else { - this.logService.info(`Rename failed because of ${getErrorMessage(error)}. Deleted from extracted location`, tempLocation); - throw error; - } + // Rename + try { + this.logService.trace(`Started renaming the extension from ${tempLocation.fsPath} to ${extensionLocation.fsPath}`); + await this.rename(tempLocation.fsPath, extensionLocation.fsPath); + this.logService.info('Renamed to', extensionLocation.fsPath); + } catch (error) { + if (error.code === 'ENOTEMPTY') { + this.logService.info(`Rename failed because extension was installed by another source. So ignoring renaming.`, extensionKey.id); + try { await this.fileService.del(tempLocation, { recursive: true }); } catch (e) { /* ignore */ } + } else { + this.logService.info(`Rename failed because of ${getErrorMessage(error)}. Deleted from extracted location`, tempLocation); + throw error; } + } - this._onExtract.fire(extensionLocation); + this._onExtract.fire(extensionLocation); - } catch (error) { - try { await this.fileService.del(tempLocation, { recursive: true }); } catch (e) { /* ignore */ } - throw error; - } + } catch (error) { + try { await this.fileService.del(tempLocation, { recursive: true }); } catch (e) { /* ignore */ } + throw error; } return this.scanLocalExtension(extensionLocation, ExtensionType.User); } - async scanMetadata(local: ILocalExtension, profileLocation?: URI): Promise { - if (profileLocation) { - const extension = await this.getScannedExtension(local, profileLocation); - return extension?.metadata; - } else { - return this.extensionsScannerService.scanMetadata(local.location); - } + async scanMetadata(local: ILocalExtension, profileLocation: URI): Promise { + const extension = await this.getScannedExtension(local, profileLocation); + return extension?.metadata; } private async getScannedExtension(local: ILocalExtension, profileLocation: URI): Promise { @@ -634,56 +702,77 @@ export class ExtensionsScanner extends Disposable { return extensions.find(e => areSameExtensions(e.identifier, local.identifier)); } - async updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation?: URI): Promise { + async updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation: URI): Promise { try { - if (profileLocation) { - await this.extensionsProfileScannerService.updateMetadata([[local, metadata]], profileLocation); - } else { - await this.extensionsScannerService.updateMetadata(local.location, metadata); - } + await this.extensionsProfileScannerService.updateMetadata([[local, metadata]], profileLocation); } catch (error) { + this.telemetryService.publicLog2('extension:extract', { extensionId: local.identifier.id, code: `${toFileOperationResult(error)}`, isProfile: !!profileLocation }); throw toExtensionManagementError(error, ExtensionManagementErrorCode.UpdateMetadata); } return this.scanLocalExtension(local.location, local.type, profileLocation); } - async getUninstalledExtensions(): Promise> { - try { - return await this.withUninstalledExtensions(); - } catch (error) { - throw toExtensionManagementError(error, ExtensionManagementErrorCode.ReadUninstalled); + async setExtensionsForRemoval(...extensions: IExtension[]): Promise { + const extensionsToRemove = []; + for (const extension of extensions) { + if (await this.fileService.exists(extension.location)) { + extensionsToRemove.push(extension); + } } - } - - async setUninstalled(...extensions: IExtension[]): Promise { - const extensionKeys: ExtensionKey[] = extensions.map(e => ExtensionKey.create(e)); - await this.withUninstalledExtensions(uninstalled => + const extensionKeys: ExtensionKey[] = extensionsToRemove.map(e => ExtensionKey.create(e)); + await this.withRemovedExtensions(removedExtensions => extensionKeys.forEach(extensionKey => { - uninstalled[extensionKey.toString()] = true; - this.logService.info('Marked extension as uninstalled', extensionKey.toString()); + removedExtensions[extensionKey.toString()] = true; + this.logService.info('Marked extension as removed', extensionKey.toString()); })); } - async setInstalled(extensionKey: ExtensionKey): Promise { + async unsetExtensionsForRemoval(...extensionKeys: ExtensionKey[]): Promise { try { - await this.withUninstalledExtensions(uninstalled => delete uninstalled[extensionKey.toString()]); + const results: boolean[] = []; + await this.withRemovedExtensions(removedExtensions => + extensionKeys.forEach(extensionKey => { + if (removedExtensions[extensionKey.toString()]) { + results.push(true); + delete removedExtensions[extensionKey.toString()]; + } else { + results.push(false); + } + })); + return results; } catch (error) { - throw toExtensionManagementError(error, ExtensionManagementErrorCode.UnsetUninstalled); + throw toExtensionManagementError(error, ExtensionManagementErrorCode.UnsetRemoved); } } - async removeExtension(extension: ILocalExtension | IScannedExtension, type: string): Promise { + async deleteExtension(extension: ILocalExtension | IScannedExtension, type: string): Promise { if (this.uriIdentityService.extUri.isEqualOrParent(extension.location, this.extensionsScannerService.userExtensionsLocation)) { - return this.deleteExtensionFromLocation(extension.identifier.id, extension.location, type); + await this.deleteExtensionFromLocation(extension.identifier.id, extension.location, type); + await this.unsetExtensionsForRemoval(ExtensionKey.create(extension)); } } - async removeUninstalledExtension(extension: ILocalExtension | IScannedExtension): Promise { - await this.removeExtension(extension, 'uninstalled'); - await this.withUninstalledExtensions(uninstalled => delete uninstalled[ExtensionKey.create(extension).toString()]); + async copyExtension(extension: ILocalExtension, fromProfileLocation: URI, toProfileLocation: URI, metadata: Partial): Promise { + const source = await this.getScannedExtension(extension, fromProfileLocation); + const target = await this.getScannedExtension(extension, toProfileLocation); + metadata = { ...source?.metadata, ...metadata }; + + if (target) { + if (this.uriIdentityService.extUri.isEqual(target.location, extension.location)) { + await this.extensionsProfileScannerService.updateMetadata([[extension, { ...target.metadata, ...metadata }]], toProfileLocation); + } else { + const targetExtension = await this.scanLocalExtension(target.location, extension.type, toProfileLocation); + await this.extensionsProfileScannerService.removeExtensionsFromProfile([targetExtension.identifier], toProfileLocation); + await this.extensionsProfileScannerService.addExtensionsToProfile([[extension, { ...target.metadata, ...metadata }]], toProfileLocation); + } + } else { + await this.extensionsProfileScannerService.addExtensionsToProfile([[extension, metadata]], toProfileLocation); + } + + return this.scanLocalExtension(extension.location, extension.type, toProfileLocation); } - async copyExtension(extension: ILocalExtension, fromProfileLocation: URI, toProfileLocation: URI, metadata: Partial): Promise { + async moveExtension(extension: ILocalExtension, fromProfileLocation: URI, toProfileLocation: URI, metadata: Partial): Promise { const source = await this.getScannedExtension(extension, fromProfileLocation); const target = await this.getScannedExtension(extension, toProfileLocation); metadata = { ...source?.metadata, ...metadata }; @@ -693,16 +782,23 @@ export class ExtensionsScanner extends Disposable { await this.extensionsProfileScannerService.updateMetadata([[extension, { ...target.metadata, ...metadata }]], toProfileLocation); } else { const targetExtension = await this.scanLocalExtension(target.location, extension.type, toProfileLocation); - await this.extensionsProfileScannerService.removeExtensionFromProfile(targetExtension, toProfileLocation); + await this.removeExtension(targetExtension.identifier, toProfileLocation); await this.extensionsProfileScannerService.addExtensionsToProfile([[extension, { ...target.metadata, ...metadata }]], toProfileLocation); } } else { await this.extensionsProfileScannerService.addExtensionsToProfile([[extension, metadata]], toProfileLocation); + if (source) { + await this.removeExtension(source.identifier, fromProfileLocation); + } } return this.scanLocalExtension(extension.location, extension.type, toProfileLocation); } + async removeExtension(identifier: IExtensionIdentifier, fromProfileLocation: URI): Promise { + await this.extensionsProfileScannerService.removeExtensionsFromProfile([identifier], fromProfileLocation); + } + async copyExtensions(fromProfileLocation: URI, toProfileLocation: URI, productVersion: IProductVersion): Promise { const fromExtensions = await this.scanExtensions(ExtensionType.User, fromProfileLocation, productVersion); const extensions: [ILocalExtension, Metadata | undefined][] = await Promise.all(fromExtensions @@ -719,11 +815,11 @@ export class ExtensionsScanner extends Disposable { this.logService.info(`Deleted ${type} extension from disk`, id, location.fsPath); } - private withUninstalledExtensions(updateFn?: (uninstalled: IStringDictionary) => void): Promise> { - return this.uninstalledFileLimiter.queue(async () => { + private withRemovedExtensions(updateFn?: (removed: IStringDictionary) => void): Promise> { + return this.obsoleteFileLimiter.queue(async () => { let raw: string | undefined; try { - const content = await this.fileService.readFile(this.uninstalledResource, 'utf8'); + const content = await this.fileService.readFile(this.obsoletedResource, 'utf8'); raw = content.value.toString(); } catch (error) { if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) { @@ -731,23 +827,29 @@ export class ExtensionsScanner extends Disposable { } } - let uninstalled = {}; + let removed = {}; if (raw) { try { - uninstalled = JSON.parse(raw); + removed = JSON.parse(raw); } catch (e) { /* ignore */ } } if (updateFn) { - updateFn(uninstalled); - if (Object.keys(uninstalled).length) { - await this.fileService.writeFile(this.uninstalledResource, VSBuffer.fromString(JSON.stringify(uninstalled))); + updateFn(removed); + if (Object.keys(removed).length) { + await this.fileService.writeFile(this.obsoletedResource, VSBuffer.fromString(JSON.stringify(removed))); } else { - await this.fileService.del(this.uninstalledResource); + try { + await this.fileService.del(this.obsoletedResource); + } catch (error) { + if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) { + throw error; + } + } } } - return uninstalled; + return removed; }); } @@ -759,7 +861,7 @@ export class ExtensionsScanner extends Disposable { } } - private async scanLocalExtension(location: URI, type: ExtensionType, profileLocation?: URI): Promise { + async scanLocalExtension(location: URI, type: ExtensionType, profileLocation?: URI): Promise { try { if (profileLocation) { const scannedExtensions = await this.extensionsScannerService.scanUserExtensions({ profileLocation }); @@ -780,10 +882,14 @@ export class ExtensionsScanner extends Disposable { } private async toLocalExtension(extension: IScannedExtension): Promise { - const stat = await this.fileService.resolve(extension.location); + let stat: IFileStat | undefined; + try { + stat = await this.fileService.resolve(extension.location); + } catch (error) {/* ignore */ } + let readmeUrl: URI | undefined; let changelogUrl: URI | undefined; - if (stat.children) { + if (stat?.children) { readmeUrl = stat.children.find(({ name }) => /^readme(\.txt|\.md|)$/i.test(name))?.resource; changelogUrl = stat.children.find(({ name }) => /^changelog(\.txt|\.md|)$/i.test(name))?.resource; } @@ -804,28 +910,47 @@ export class ExtensionsScanner extends Disposable { isMachineScoped: !!extension.metadata?.isMachineScoped, isPreReleaseVersion: !!extension.metadata?.isPreReleaseVersion, hasPreReleaseVersion: !!extension.metadata?.hasPreReleaseVersion, - preRelease: !!extension.metadata?.preRelease, + preRelease: extension.preRelease, installedTimestamp: extension.metadata?.installedTimestamp, updated: !!extension.metadata?.updated, pinned: !!extension.metadata?.pinned, + private: !!extension.metadata?.private, isWorkspaceScoped: false, - source: extension.metadata?.source ?? (extension.identifier.uuid ? 'gallery' : 'vsix') + source: extension.metadata?.source ?? (extension.identifier.uuid ? 'gallery' : 'vsix'), + size: extension.metadata?.size ?? 0, }; } - private async removeUninstalledExtensions(): Promise { - const uninstalled = await this.getUninstalledExtensions(); - if (Object.keys(uninstalled).length === 0) { - this.logService.debug(`No uninstalled extensions found.`); + private async initializeExtensionSize(): Promise { + const extensions = await this.extensionsScannerService.scanAllUserExtensions(); + await Promise.all(extensions.map(async extension => { + // set size if not set before + if (isDefined(extension.metadata?.installedTimestamp) && isUndefined(extension.metadata?.size)) { + const size = await computeSize(extension.location, this.fileService); + await this.extensionsScannerService.updateManifestMetadata(extension.location, { size }); + } + })); + } + + private async deleteExtensionsMarkedForRemoval(): Promise { + let removed: IStringDictionary; + try { + removed = await this.withRemovedExtensions(); + } catch (error) { + throw toExtensionManagementError(error, ExtensionManagementErrorCode.ReadRemoved); + } + + if (Object.keys(removed).length === 0) { + this.logService.debug(`No extensions are marked as removed.`); return; } - this.logService.debug(`Removing uninstalled extensions:`, Object.keys(uninstalled)); + this.logService.debug(`Deleting extensions marked as removed:`, Object.keys(removed)); - const extensions = await this.extensionsScannerService.scanUserExtensions({ includeAllVersions: true, includeUninstalled: true, includeInvalid: true }); // All user extensions + const extensions = await this.scanAllUserExtensions(); const installed: Set = new Set(); for (const e of extensions) { - if (!uninstalled[ExtensionKey.create(e).toString()]) { + if (!removed[ExtensionKey.create(e).toString()]) { installed.add(e.identifier.id.toLowerCase()); } } @@ -836,15 +961,15 @@ export class ExtensionsScanner extends Disposable { await Promises.settled(byExtension.map(async e => { const latest = e.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version))[0]; if (!installed.has(latest.identifier.id.toLowerCase())) { - await this.beforeRemovingExtension(await this.toLocalExtension(latest)); + await this.beforeRemovingExtension(latest); } })); } catch (error) { this.logService.error(error); } - const toRemove = extensions.filter(e => e.metadata /* Installed by System */ && uninstalled[ExtensionKey.create(e).toString()]); - await Promise.allSettled(toRemove.map(e => this.removeUninstalledExtension(e))); + const toRemove = extensions.filter(e => e.installedTimestamp /* Installed by System */ && removed[ExtensionKey.create(e).toString()]); + await Promise.allSettled(toRemove.map(e => this.deleteExtension(e, 'marked for removal'))); } private async removeTemporarilyDeletedFolders(): Promise { @@ -889,7 +1014,7 @@ class InstallExtensionInProfileTask extends AbstractExtensionTask { - const installed = await this.extensionsScanner.scanExtensions(null, this.options.profileLocation, this.options.productVersion); - return installed.find(i => areSameExtensions(i.identifier, this.identifier)); - } - protected async doRun(token: CancellationToken): Promise { - const existingExtension = await this.getExistingExtension(); + const installed = await this.extensionsScanner.scanExtensions(ExtensionType.User, this.options.profileLocation, this.options.productVersion); + const existingExtension = installed.find(i => areSameExtensions(i.identifier, this.identifier)); if (existingExtension) { this._operation = InstallOperation.Update; } @@ -940,21 +1061,20 @@ class InstallExtensionInProfileTask extends AbstractExtensionTask { - const uninstalled = await this.extensionsScanner.getUninstalledExtensions(); - if (!uninstalled[extensionKey.toString()]) { - return undefined; + private async unsetIfRemoved(extensionKey: ExtensionKey): Promise { + // If the same version of extension is marked as removed, remove it from there and return the local. + const [removed] = await this.extensionsScanner.unsetExtensionsForRemoval(extensionKey); + if (removed) { + this.logService.info('Removed the extension from removed list:', extensionKey.id); + const userExtensions = await this.extensionsScanner.scanAllUserExtensions(); + return userExtensions.find(i => ExtensionKey.create(i).equals(extensionKey)); } - - this.logService.trace('Removing the extension from uninstalled list:', extensionKey.id); - // If the same version of extension is marked as uninstalled, remove it from there and return the local. - await this.extensionsScanner.setInstalled(extensionKey); - this.logService.info('Removed the extension from uninstalled list:', extensionKey.id); - - const userExtensions = await this.extensionsScanner.scanAllUserExtensions(true); - return userExtensions.find(i => ExtensionKey.create(i).equals(extensionKey)); + return undefined; } private async updateMetadata(extension: ILocalExtension, token: CancellationToken): Promise { @@ -1063,14 +1180,14 @@ class UninstallExtensionInProfileTask extends AbstractExtensionTask implem constructor( readonly extension: ILocalExtension, - private readonly profileLocation: URI, + readonly options: UninstallExtensionTaskOptions, private readonly extensionsProfileScannerService: IExtensionsProfileScannerService, ) { super(); } - protected async doRun(token: CancellationToken): Promise { - await this.extensionsProfileScannerService.removeExtensionFromProfile(this.extension, this.profileLocation); + protected doRun(token: CancellationToken): Promise { + return this.extensionsProfileScannerService.removeExtensionsFromProfile([this.extension.identifier], this.options.profileLocation); } } diff --git a/patched-vscode/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts b/patched-vscode/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts index 343051cce..c7bd0d1a2 100644 --- a/patched-vscode/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts +++ b/patched-vscode/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts @@ -16,7 +16,6 @@ import { getServiceMachineId } from 'vs/platform/externalServices/common/service import { IStorageService } from 'vs/platform/storage/common/storage'; import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { getTelemetryLevel, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; -import { RemoteAuthorities } from 'vs/base/common/network'; import { TargetPlatform } from 'vs/platform/extensions/common/extensions'; const WEB_EXTENSION_RESOURCE_END_POINT_SEGMENT = '/web-extension-resource/'; @@ -141,9 +140,9 @@ export abstract class AbstractExtensionResourceLoaderService implements IExtensi } protected _isWebExtensionResourceEndPoint(uri: URI): boolean { - const uriPath = uri.path, serverRootPath = RemoteAuthorities.getServerRootPath(); - // test if the path starts with the server root path followed by the web extension resource end point segment - return uriPath.startsWith(serverRootPath) && uriPath.startsWith(WEB_EXTENSION_RESOURCE_END_POINT_SEGMENT, serverRootPath.length); + const uriPath = uri.path; + // test if the path starts with the web extension resource end point segment + return uriPath.startsWith(WEB_EXTENSION_RESOURCE_END_POINT_SEGMENT); } } diff --git a/patched-vscode/src/vs/platform/languagePacks/browser/languagePacks.ts b/patched-vscode/src/vs/platform/languagePacks/browser/languagePacks.ts index 62dedb224..30e9b6593 100644 --- a/patched-vscode/src/vs/platform/languagePacks/browser/languagePacks.ts +++ b/patched-vscode/src/vs/platform/languagePacks/browser/languagePacks.ts @@ -3,20 +3,26 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { URI } from 'vs/base/common/uri'; -import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IExtensionResourceLoaderService } from 'vs/platform/extensionResourceLoader/common/extensionResourceLoader'; -import { ILanguagePackItem, LanguagePackBaseService } from 'vs/platform/languagePacks/common/languagePacks'; -import { ILogService } from 'vs/platform/log/common/log'; +import { CancellationTokenSource } from '../../../base/common/cancellation.js'; +import { URI } from '../../../base/common/uri.js'; +import { ProxyChannel } from '../../../base/parts/ipc/common/ipc.js'; +import { IExtensionGalleryService } from '../../extensionManagement/common/extensionManagement.js'; +import { IExtensionResourceLoaderService } from '../../extensionResourceLoader/common/extensionResourceLoader.js'; +import { ILanguagePackItem, ILanguagePackService, LanguagePackBaseService } from '../common/languagePacks.js'; +import { ILogService } from '../../log/common/log.js'; +import { IRemoteAgentService } from '../../../workbench/services/remote/common/remoteAgentService.js'; export class WebLanguagePacksService extends LanguagePackBaseService { + private readonly languagePackService: ILanguagePackService; + constructor( + @IRemoteAgentService remoteAgentService: IRemoteAgentService, @IExtensionResourceLoaderService private readonly extensionResourceLoaderService: IExtensionResourceLoaderService, @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, @ILogService private readonly logService: ILogService ) { super(extensionGalleryService); + this.languagePackService = ProxyChannel.toService(remoteAgentService.getConnection()!.getChannel('languagePacks')) } async getBuiltInExtensionTranslationsUri(id: string, language: string): Promise { @@ -56,7 +62,7 @@ export class WebLanguagePacksService extends LanguagePackBaseService { } // get the resource uri and return it - const uri = this.extensionResourceLoaderService.getExtensionGalleryResourceURL({ + const uri = await this.extensionResourceLoaderService.getExtensionGalleryResourceURL({ // If translation is defined then manifest should have been defined. name: manifest!.name, publisher: manifest!.publisher, @@ -72,6 +78,6 @@ export class WebLanguagePacksService extends LanguagePackBaseService { // Web doesn't have a concept of language packs, so we just return an empty array getInstalledLanguages(): Promise { - return Promise.resolve([]); + return this.languagePackService.getInstalledLanguages() } } diff --git a/patched-vscode/src/vs/platform/product/common/product.ts b/patched-vscode/src/vs/platform/product/common/product.ts index 7f5f731e9..f7000d38e 100644 --- a/patched-vscode/src/vs/platform/product/common/product.ts +++ b/patched-vscode/src/vs/platform/product/common/product.ts @@ -47,6 +47,27 @@ else if (globalThis._VSCODE_PRODUCT_JSON && globalThis._VSCODE_PACKAGE_JSON) { version: pkg.version }); } + + if (env['EXTENSIONS_GALLERY']) { + console.log(`Custom extensions gallery detected. Parsing...`); + Object.assign(product, { + extensionsGallery: JSON.parse(env['EXTENSIONS_GALLERY']) + }); + } else { + console.log(`Using default extensions gallery.`); + Object.assign(product, { + extensionsGallery: (product.extensionsGallery || { + serviceUrl: "https://open-vsx.org/vscode/gallery", + itemUrl: "https://open-vsx.org/vscode/item", + resourceUrlTemplate: "https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}", + controlUrl: "", + recommendationsUrl: "", + nlsBaseUrl: "", + publisherUrl: "" + }) + }); + } + console.log(JSON.stringify(product.extensionsGallery, null, 2)); } // Web environment or unknown diff --git a/patched-vscode/src/vs/server/node/remoteLanguagePacks.ts b/patched-vscode/src/vs/server/node/remoteLanguagePacks.ts index 682b6f2b0..ba6b17125 100644 --- a/patched-vscode/src/vs/server/node/remoteLanguagePacks.ts +++ b/patched-vscode/src/vs/server/node/remoteLanguagePacks.ts @@ -8,36 +8,30 @@ import { FileAccess } from 'vs/base/common/network'; import * as path from 'vs/base/common/path'; import * as lp from 'vs/base/node/languagePacks'; -import product from 'vs/platform/product/common/product'; const metaData = path.join(FileAccess.asFileUri('').fsPath, 'nls.metadata.json'); const _cache: Map> = new Map(); -function exists(file: string) { - return new Promise(c => fs.exists(file, c)); -} - export function getNLSConfiguration(language: string, userDataPath: string): Promise { - return exists(metaData).then((fileExists) => { - if (!fileExists || !product.commit) { - // console.log(`==> MetaData or commit unknown. Using default language.`); - // The OS Locale on the remote side really doesn't matter, so we return the default locale - return Promise.resolve({ locale: 'en', osLocale: 'en', availableLanguages: {} }); - } - const key = `${language}||${userDataPath}`; - let result = _cache.get(key); - if (!result) { - // The OS Locale on the remote side really doesn't matter, so we pass in the same language - result = lp.getNLSConfiguration(product.commit, userDataPath, metaData, language, language).then(value => { - if (InternalNLSConfiguration.is(value)) { - value._languagePackSupport = true; - } - return value; - }); - _cache.set(key, result); - } - return result; - }); + const key = `${language}||${userDataPath}`; + let result = _cache.get(key); + if (!result) { + // The OS Locale on the remote side really doesn't matter, so we pass in the same language + result = lp.getNLSConfiguration("dummy_commit", userDataPath, metaData, language, language).then(value => { + if (InternalNLSConfiguration.is(value)) { + value._languagePackSupport = true; + } + // If the configuration has no results keep trying since code-server + // doesn't restart when a language is installed so this result would + // persist (the plugin might not be installed yet for example). + if (value.locale !== 'en' && value.locale !== 'en-us' && Object.keys(value.availableLanguages).length === 0) { + _cache.delete(key); + } + return value; + }); + _cache.set(key, result); + } + return result; } export namespace InternalNLSConfiguration { @@ -46,3 +40,43 @@ export namespace InternalNLSConfiguration { return candidate && typeof candidate._languagePackId === 'string'; } } + +/** + * The code below is copied from from src/main.js. + */ + +export const getLocaleFromConfig = async (argvResource: string): Promise => { + try { + const content = stripComments(await fs.promises.readFile(argvResource, 'utf8')); + return JSON.parse(content).locale; + } catch (error) { + if (error.code !== "ENOENT") { + console.warn(error) + } + return 'en'; + } +}; + +const stripComments = (content: string): string => { + const regexp = /('(?:[^\\']*(?:\\.)?)*')|('(?:[^\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g; + + return content.replace(regexp, (match, _m1, _m2, m3, m4) => { + // Only one of m1, m2, m3, m4 matches + if (m3) { + // A block comment. Replace with nothing + return ''; + } else if (m4) { + // A line comment. If it ends in \r?\n then keep it. + const length_1 = m4.length; + if (length_1 > 2 && m4[length_1 - 1] === '\n') { + return m4[length_1 - 2] === '\r' ? '\r\n' : '\n'; + } + else { + return ''; + } + } else { + // We match a string + return match; + } + }); +}; \ No newline at end of file diff --git a/patched-vscode/src/vs/server/node/serverEnvironmentService.ts b/patched-vscode/src/vs/server/node/serverEnvironmentService.ts index 83d4aac73..d13d84583 100644 --- a/patched-vscode/src/vs/server/node/serverEnvironmentService.ts +++ b/patched-vscode/src/vs/server/node/serverEnvironmentService.ts @@ -3,17 +3,20 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import * as nls from '../../nls.js'; -import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; -import { OPTIONS, OptionDescriptions } from 'vs/platform/environment/node/argv'; -import { refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; -import { memoize } from 'vs/base/common/decorators'; -import { URI } from 'vs/base/common/uri'; +import { NativeEnvironmentService } from '../../platform/environment/node/environmentService.js'; +import { OPTIONS, OptionDescriptions } from '../../platform/environment/node/argv.js'; +import { refineServiceDecorator } from '../../platform/instantiation/common/instantiation.js'; +import { IEnvironmentService, INativeEnvironmentService } from '../../platform/environment/common/environment.js'; +import { IProductService } from '../../platform/product/common/productService.js'; +import { memoize } from '../../base/common/decorators.js'; +import { URI } from '../../base/common/uri.js'; export const serverOptions: OptionDescriptions> = { + 'locale': { type: 'string' }, + /* ----- server setup ----- */ 'host': { type: 'string', cat: 'o', args: 'ip-address', description: nls.localize('host', "The host name or IP address the server should listen to. If not set, defaults to 'localhost'.") }, @@ -40,6 +43,7 @@ export const serverOptions: OptionDescriptions> = { 'log': OPTIONS['log'], 'logsPath': OPTIONS['logsPath'], 'force-disable-user-env': OPTIONS['force-disable-user-env'], + 'enable-proposed-api': OPTIONS['enable-proposed-api'], /* ----- vs code web options ----- */ @@ -69,6 +73,7 @@ export const serverOptions: OptionDescriptions> = { 'category': OPTIONS['category'], 'force': OPTIONS['force'], 'do-not-sync': OPTIONS['do-not-sync'], + 'do-not-include-pack-dependencies': OPTIONS['do-not-include-pack-dependencies'], 'pre-release': OPTIONS['pre-release'], 'start-server': { type: 'boolean', cat: 'e', description: nls.localize('start-server', "Start the server when installing or uninstalling extensions. To be used in combination with 'install-extension', 'install-builtin-extension' and 'uninstall-extension'.") }, @@ -97,6 +102,8 @@ export const serverOptions: OptionDescriptions> = { export interface ServerParsedArgs { + 'locale'?: string; + /* ----- server setup ----- */ host?: string; @@ -165,6 +172,7 @@ export interface ServerParsedArgs { 'logsPath'?: string; 'force-disable-user-env'?: boolean; + 'enable-proposed-api'?: string[]; /* ----- vs code web options ----- */ @@ -197,6 +205,8 @@ export interface ServerParsedArgs { force?: boolean; // used by install-extension 'do-not-sync'?: boolean; // used by install-extension 'pre-release'?: boolean; // used by install-extension + 'do-not-include-pack-dependencies'?: boolean; // used by install-extension + 'start-server'?: boolean; @@ -228,7 +238,17 @@ export interface IServerEnvironmentService extends INativeEnvironmentService { } export class ServerEnvironmentService extends NativeEnvironmentService implements IServerEnvironmentService { + + constructor(args: ServerParsedArgs, productService: IProductService) { + /* ----- sagemaker compatibility: map --base-path to --server-base-path ----- */ + if (args['base-path']) { + args['server-base-path'] = args['base-path']; + } + + super(args, productService); + } + @memoize override get userRoamingDataHome(): URI { return this.appSettingsHome; } override get args(): ServerParsedArgs { return super.args as ServerParsedArgs; } -} +} \ No newline at end of file diff --git a/patched-vscode/src/vs/server/node/serverServices.ts b/patched-vscode/src/vs/server/node/serverServices.ts index 46f2f0046..ccfe4aef7 100644 --- a/patched-vscode/src/vs/server/node/serverServices.ts +++ b/patched-vscode/src/vs/server/node/serverServices.ts @@ -4,79 +4,87 @@ *--------------------------------------------------------------------------------------------*/ import { hostname, release } from 'os'; -import { Emitter, Event } from 'vs/base/common/event'; -import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; -import { Schemas } from 'vs/base/common/network'; -import * as path from 'vs/base/common/path'; -import { IURITransformer } from 'vs/base/common/uriIpc'; -import { getMachineId, getSqmMachineId, getdevDeviceId } from 'vs/base/node/id'; -import { Promises } from 'vs/base/node/pfs'; -import { ClientConnectionEvent, IMessagePassingProtocol, IPCServer, StaticRouter } from 'vs/base/parts/ipc/common/ipc'; -import { ProtocolConstants } from 'vs/base/parts/ipc/common/ipc.net'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; -import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; -import { IDownloadService } from 'vs/platform/download/common/download'; -import { DownloadServiceChannelClient } from 'vs/platform/download/common/downloadIpc'; -import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ExtensionGalleryServiceWithNoStorageService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; -import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { ExtensionSignatureVerificationService, IExtensionSignatureVerificationService } from 'vs/platform/extensionManagement/node/extensionSignatureVerificationService'; -import { ExtensionManagementCLI } from 'vs/platform/extensionManagement/common/extensionManagementCLI'; -import { ExtensionManagementChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; -import { ExtensionManagementService, INativeServerExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; -import { IFileService } from 'vs/platform/files/common/files'; -import { FileService } from 'vs/platform/files/common/fileService'; -import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; -import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; -import { NativeLanguagePackService } from 'vs/platform/languagePacks/node/languagePacks'; -import { AbstractLogger, DEFAULT_LOG_LEVEL, getLogLevel, ILoggerService, ILogService, log, LogLevel, LogLevelToString } from 'vs/platform/log/common/log'; -import product from 'vs/platform/product/common/product'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment'; -import { IRequestService } from 'vs/platform/request/common/request'; -import { RequestChannel } from 'vs/platform/request/common/requestIpc'; -import { RequestService } from 'vs/platform/request/node/requestService'; -import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties'; -import { ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; -import { ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; -import { getPiiPathsFromEnvironment, isInternalTelemetry, isLoggingOnly, ITelemetryAppender, NullAppender, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; -import ErrorTelemetry from 'vs/platform/telemetry/node/errorTelemetry'; -import { IPtyService, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { PtyHostService } from 'vs/platform/terminal/node/ptyHostService'; -import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; -import { RemoteAgentEnvironmentChannel } from 'vs/server/node/remoteAgentEnvironmentImpl'; -import { RemoteAgentFileSystemProviderChannel } from 'vs/server/node/remoteFileSystemProviderServer'; -import { ServerTelemetryChannel } from 'vs/platform/telemetry/common/remoteTelemetryChannel'; -import { IServerTelemetryService, ServerNullTelemetryService, ServerTelemetryService } from 'vs/platform/telemetry/common/serverTelemetryService'; -import { RemoteTerminalChannel } from 'vs/server/node/remoteTerminalChannel'; -import { createURITransformer } from 'vs/workbench/api/node/uriTransformer'; -import { ServerConnectionToken } from 'vs/server/node/serverConnectionToken'; -import { ServerEnvironmentService, ServerParsedArgs } from 'vs/server/node/serverEnvironmentService'; -import { REMOTE_TERMINAL_CHANNEL_NAME } from 'vs/workbench/contrib/terminal/common/remote/remoteTerminalChannel'; -import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from 'vs/workbench/services/remote/common/remoteFileSystemProviderClient'; -import { ExtensionHostStatusService, IExtensionHostStatusService } from 'vs/server/node/extensionHostStatusService'; -import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService'; -import { ExtensionsScannerService } from 'vs/server/node/extensionsScannerService'; -import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; -import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { NullPolicyService } from 'vs/platform/policy/common/policy'; -import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender'; -import { LoggerService } from 'vs/platform/log/node/loggerService'; -import { ServerUserDataProfilesService } from 'vs/platform/userDataProfile/node/userDataProfile'; -import { ExtensionsProfileScannerService } from 'vs/platform/extensionManagement/node/extensionsProfileScannerService'; -import { LogService } from 'vs/platform/log/common/logService'; -import { LoggerChannel } from 'vs/platform/log/common/logIpc'; -import { localize } from 'vs/nls'; -import { RemoteExtensionsScannerChannel, RemoteExtensionsScannerService } from 'vs/server/node/remoteExtensionsScanner'; -import { RemoteExtensionsScannerChannelName } from 'vs/platform/remote/common/remoteExtensionsScanner'; -import { RemoteUserDataProfilesServiceChannel } from 'vs/platform/userDataProfile/common/userDataProfileIpc'; -import { NodePtyHostStarter } from 'vs/platform/terminal/node/nodePtyHostStarter'; +import { Emitter, Event } from '../../base/common/event.js'; +import { DisposableStore, toDisposable } from '../../base/common/lifecycle.js'; +import { Schemas } from '../../base/common/network.js'; +import * as path from '../../base/common/path.js'; +import { IURITransformer } from '../../base/common/uriIpc.js'; +import { getMachineId, getSqmMachineId, getdevDeviceId } from '../../base/node/id.js'; +import { Promises } from '../../base/node/pfs.js'; +import { ClientConnectionEvent, IMessagePassingProtocol, IPCServer, ProxyChannel, StaticRouter } from '../../base/parts/ipc/common/ipc.js'; +import { ProtocolConstants } from '../../base/parts/ipc/common/ipc.net.js'; +import { IConfigurationService } from '../../platform/configuration/common/configuration.js'; +import { ConfigurationService } from '../../platform/configuration/common/configurationService.js'; +import { ExtensionHostDebugBroadcastChannel } from '../../platform/debug/common/extensionHostDebugIpc.js'; +import { IDownloadService } from '../../platform/download/common/download.js'; +import { DownloadServiceChannelClient } from '../../platform/download/common/downloadIpc.js'; +import { IEnvironmentService, INativeEnvironmentService } from '../../platform/environment/common/environment.js'; +import { ExtensionGalleryServiceWithNoStorageService } from '../../platform/extensionManagement/common/extensionGalleryService.js'; +import { IAllowedExtensionsService, IExtensionGalleryService } from '../../platform/extensionManagement/common/extensionManagement.js'; +import { ExtensionSignatureVerificationService, IExtensionSignatureVerificationService } from '../../platform/extensionManagement/node/extensionSignatureVerificationService.js'; +import { ExtensionManagementCLI } from '../../platform/extensionManagement/common/extensionManagementCLI.js'; +import { ExtensionManagementChannel } from '../../platform/extensionManagement/common/extensionManagementIpc.js'; +import { ExtensionManagementService, INativeServerExtensionManagementService } from '../../platform/extensionManagement/node/extensionManagementService.js'; +import { IFileService } from '../../platform/files/common/files.js'; +import { FileService } from '../../platform/files/common/fileService.js'; +import { DiskFileSystemProvider } from '../../platform/files/node/diskFileSystemProvider.js'; +import { SyncDescriptor } from '../../platform/instantiation/common/descriptors.js'; +import { IInstantiationService } from '../../platform/instantiation/common/instantiation.js'; +import { InstantiationService } from '../../platform/instantiation/common/instantiationService.js'; +import { ServiceCollection } from '../../platform/instantiation/common/serviceCollection.js'; +import { ILanguagePackService } from '../../platform/languagePacks/common/languagePacks.js'; +import { NativeLanguagePackService } from '../../platform/languagePacks/node/languagePacks.js'; +import { AbstractLogger, DEFAULT_LOG_LEVEL, getLogLevel, ILoggerService, ILogService, log, LogLevel, LogLevelToString } from '../../platform/log/common/log.js'; +import product from '../../platform/product/common/product.js'; +import { IProductService } from '../../platform/product/common/productService.js'; +import { RemoteAgentConnectionContext } from '../../platform/remote/common/remoteAgentEnvironment.js'; +import { IRequestService } from '../../platform/request/common/request.js'; +import { RequestChannel } from '../../platform/request/common/requestIpc.js'; +import { RequestService } from '../../platform/request/node/requestService.js'; +import { resolveCommonProperties } from '../../platform/telemetry/common/commonProperties.js'; +import { ITelemetryService, TelemetryLevel } from '../../platform/telemetry/common/telemetry.js'; +import { ITelemetryServiceConfig } from '../../platform/telemetry/common/telemetryService.js'; +import { getPiiPathsFromEnvironment, isInternalTelemetry, isLoggingOnly, ITelemetryAppender, NullAppender, supportsTelemetry } from '../../platform/telemetry/common/telemetryUtils.js'; +import ErrorTelemetry from '../../platform/telemetry/node/errorTelemetry.js'; +import { IPtyService, TerminalSettingId } from '../../platform/terminal/common/terminal.js'; +import { PtyHostService } from '../../platform/terminal/node/ptyHostService.js'; +import { IUriIdentityService } from '../../platform/uriIdentity/common/uriIdentity.js'; +import { UriIdentityService } from '../../platform/uriIdentity/common/uriIdentityService.js'; +import { RemoteAgentEnvironmentChannel } from './remoteAgentEnvironmentImpl.js'; +import { RemoteAgentFileSystemProviderChannel } from './remoteFileSystemProviderServer.js'; +import { ServerTelemetryChannel } from '../../platform/telemetry/common/remoteTelemetryChannel.js'; +import { IServerTelemetryService, ServerNullTelemetryService, ServerTelemetryService } from '../../platform/telemetry/common/serverTelemetryService.js'; +import { RemoteTerminalChannel } from './remoteTerminalChannel.js'; +import { createURITransformer } from '../../workbench/api/node/uriTransformer.js'; +import { ServerConnectionToken } from './serverConnectionToken.js'; +import { ServerEnvironmentService, ServerParsedArgs } from './serverEnvironmentService.js'; +import { REMOTE_TERMINAL_CHANNEL_NAME } from '../../workbench/contrib/terminal/common/remote/remoteTerminalChannel.js'; +import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from '../../workbench/services/remote/common/remoteFileSystemProviderClient.js'; +import { ExtensionHostStatusService, IExtensionHostStatusService } from './extensionHostStatusService.js'; +import { IExtensionsScannerService } from '../../platform/extensionManagement/common/extensionsScannerService.js'; +import { ExtensionsScannerService } from './extensionsScannerService.js'; +import { IExtensionsProfileScannerService } from '../../platform/extensionManagement/common/extensionsProfileScannerService.js'; +import { IUserDataProfilesService } from '../../platform/userDataProfile/common/userDataProfile.js'; +import { NullPolicyService } from '../../platform/policy/common/policy.js'; +import { OneDataSystemAppender } from '../../platform/telemetry/node/1dsAppender.js'; +import { LoggerService } from '../../platform/log/node/loggerService.js'; +import { ServerUserDataProfilesService } from '../../platform/userDataProfile/node/userDataProfile.js'; +import { ExtensionsProfileScannerService } from '../../platform/extensionManagement/node/extensionsProfileScannerService.js'; +import { LogService } from '../../platform/log/common/logService.js'; +import { LoggerChannel } from '../../platform/log/common/logIpc.js'; +import { localize } from '../../nls.js'; +import { RemoteExtensionsScannerChannel, RemoteExtensionsScannerService } from './remoteExtensionsScanner.js'; +import { RemoteExtensionsScannerChannelName } from '../../platform/remote/common/remoteExtensionsScanner.js'; +import { RemoteUserDataProfilesServiceChannel } from '../../platform/userDataProfile/common/userDataProfileIpc.js'; +import { NodePtyHostStarter } from '../../platform/terminal/node/nodePtyHostStarter.js'; +import { CSSDevelopmentService, ICSSDevelopmentService } from '../../platform/cssDev/node/cssDevService.js'; +import { AllowedExtensionsService } from '../../platform/extensionManagement/common/allowedExtensionsService.js'; +import { TelemetryLogAppender } from '../../platform/telemetry/common/telemetryLogAppender.js'; +import { INativeMcpDiscoveryHelperService, NativeMcpDiscoveryHelperChannelName } from '../../platform/mcp/common/nativeMcpDiscoveryHelper.js'; +import { NativeMcpDiscoveryHelperChannel } from '../../platform/mcp/node/nativeMcpDiscoveryHelperChannel.js'; +import { NativeMcpDiscoveryHelperService } from '../../platform/mcp/node/nativeMcpDiscoveryHelperService.js'; +import { IExtensionGalleryManifestService } from '../../platform/extensionManagement/common/extensionGalleryManifest.js'; +import { ExtensionGalleryManifestIPCService } from '../../platform/extensionManagement/common/extensionGalleryManifestServiceIpc.js'; const eventPrefix = 'monacoworkbench'; @@ -131,6 +139,9 @@ export async function setupServerServices(connectionToken: ServerConnectionToken services.set(IUserDataProfilesService, userDataProfilesService); socketServer.registerChannel('userDataProfiles', new RemoteUserDataProfilesServiceChannel(userDataProfilesService, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority))); + // Dev Only: CSS service (for ESM) + services.set(ICSSDevelopmentService, new SyncDescriptor(CSSDevelopmentService, undefined, true)); + // Initialize const [, , machineId, sqmId, devDeviceId] = await Promise.all([ configurationService.initialize(), @@ -144,7 +155,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken services.set(IExtensionHostStatusService, extensionHostStatusService); // Request - const requestService = new RequestService(configurationService, environmentService, logService, loggerService); + const requestService = new RequestService('remote', configurationService, environmentService, logService); services.set(IRequestService, requestService); let oneDsAppender: ITelemetryAppender = NullAppender; @@ -156,7 +167,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken } const config: ITelemetryServiceConfig = { - appenders: [oneDsAppender], + appenders: [oneDsAppender, new TelemetryLogAppender('', true, loggerService, environmentService, productService)], commonProperties: resolveCommonProperties(release(), hostname(), process.arch, productService.commit, productService.version + '-remote', machineId, sqmId, devDeviceId, isInternal, 'remoteAgent'), piiPaths: getPiiPathsFromEnvironment(environmentService) }; @@ -177,6 +188,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken services.set(IServerTelemetryService, ServerNullTelemetryService); } + services.set(IExtensionGalleryManifestService, new ExtensionGalleryManifestIPCService(socketServer, productService)); services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryServiceWithNoStorageService)); const downloadChannel = socketServer.getChannel('download', router); @@ -185,7 +197,9 @@ export async function setupServerServices(connectionToken: ServerConnectionToken services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService)); services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService)); services.set(IExtensionSignatureVerificationService, new SyncDescriptor(ExtensionSignatureVerificationService)); + services.set(IAllowedExtensionsService, new SyncDescriptor(AllowedExtensionsService)); services.set(INativeServerExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); + services.set(INativeMcpDiscoveryHelperService, new SyncDescriptor(NativeMcpDiscoveryHelperService)); const instantiationService: IInstantiationService = new InstantiationService(services); services.set(ILanguagePackService, instantiationService.createInstance(NativeLanguagePackService)); @@ -214,10 +228,12 @@ export async function setupServerServices(connectionToken: ServerConnectionToken socketServer.registerChannel(REMOTE_TERMINAL_CHANNEL_NAME, new RemoteTerminalChannel(environmentService, logService, ptyHostService, productService, extensionManagementService, configurationService)); - const remoteExtensionsScanner = new RemoteExtensionsScannerService(instantiationService.createInstance(ExtensionManagementCLI, logService), environmentService, userDataProfilesService, extensionsScannerService, logService, extensionGalleryService, languagePackService); + const remoteExtensionsScanner = new RemoteExtensionsScannerService(instantiationService.createInstance(ExtensionManagementCLI, logService), environmentService, userDataProfilesService, extensionsScannerService, logService, extensionGalleryService, languagePackService, extensionManagementService); socketServer.registerChannel(RemoteExtensionsScannerChannelName, new RemoteExtensionsScannerChannel(remoteExtensionsScanner, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority))); - const remoteFileSystemChannel = disposables.add(new RemoteAgentFileSystemProviderChannel(logService, environmentService)); + socketServer.registerChannel(NativeMcpDiscoveryHelperChannelName, instantiationService.createInstance(NativeMcpDiscoveryHelperChannel, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority))); + + const remoteFileSystemChannel = disposables.add(new RemoteAgentFileSystemProviderChannel(logService, environmentService, configurationService)); socketServer.registerChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME, remoteFileSystemChannel); socketServer.registerChannel('request', new RequestChannel(accessor.get(IRequestService))); @@ -225,6 +241,9 @@ export async function setupServerServices(connectionToken: ServerConnectionToken const channel = new ExtensionManagementChannel(extensionManagementService, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority)); socketServer.registerChannel('extensions', channel); + const languagePackChannel = ProxyChannel.fromService(accessor.get(ILanguagePackService), disposables); + socketServer.registerChannel('languagePacks', languagePackChannel); + // clean up extensions folder remoteExtensionsScanner.whenExtensionsReady().then(() => extensionManagementService.cleanUp()); @@ -272,7 +291,7 @@ class ServerLogger extends AbstractLogger { } trace(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Trace)) { + if (this.canLog(LogLevel.Trace)) { if (this.useColors) { console.log(`\x1b[90m[${now()}]\x1b[0m`, message, ...args); } else { @@ -282,7 +301,7 @@ class ServerLogger extends AbstractLogger { } debug(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Debug)) { + if (this.canLog(LogLevel.Debug)) { if (this.useColors) { console.log(`\x1b[90m[${now()}]\x1b[0m`, message, ...args); } else { @@ -292,7 +311,7 @@ class ServerLogger extends AbstractLogger { } info(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Info)) { + if (this.canLog(LogLevel.Info)) { if (this.useColors) { console.log(`\x1b[90m[${now()}]\x1b[0m`, message, ...args); } else { @@ -302,7 +321,7 @@ class ServerLogger extends AbstractLogger { } warn(message: string | Error, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Warning)) { + if (this.canLog(LogLevel.Warning)) { if (this.useColors) { console.warn(`\x1b[93m[${now()}]\x1b[0m`, message, ...args); } else { @@ -312,7 +331,7 @@ class ServerLogger extends AbstractLogger { } error(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Error)) { + if (this.canLog(LogLevel.Error)) { if (this.useColors) { console.error(`\x1b[91m[${now()}]\x1b[0m`, message, ...args); } else { diff --git a/patched-vscode/src/vs/server/node/webClientServer.ts b/patched-vscode/src/vs/server/node/webClientServer.ts index 670d3bf0f..abb3138f1 100644 --- a/patched-vscode/src/vs/server/node/webClientServer.ts +++ b/patched-vscode/src/vs/server/node/webClientServer.ts @@ -3,33 +3,33 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createReadStream } from 'fs'; -import {readFile } from 'fs/promises'; -import { Promises } from 'vs/base/node/pfs'; -import * as path from 'path'; +import { createReadStream, promises, existsSync, writeFileSync } from 'fs'; import * as http from 'http'; +import { spawn } from 'child_process'; +import * as fs from 'fs'; import * as url from 'url'; import * as cookie from 'cookie'; import * as crypto from 'crypto'; -import { isEqualOrParent } from 'vs/base/common/extpath'; -import { getMediaMime } from 'vs/base/common/mime'; -import { isLinux } from 'vs/base/common/platform'; -import { ILogService } from 'vs/platform/log/common/log'; -import { IServerEnvironmentService } from 'vs/server/node/serverEnvironmentService'; -import { extname, dirname, join, normalize } from 'vs/base/common/path'; -import { FileAccess, connectionTokenCookieName, connectionTokenQueryName, Schemas, builtinExtensionsPath } from 'vs/base/common/network'; -import { generateUuid } from 'vs/base/common/uuid'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { ServerConnectionToken, ServerConnectionTokenType } from 'vs/server/node/serverConnectionToken'; -import { asTextOrError, IRequestService } from 'vs/platform/request/common/request'; -import { IHeaders } from 'vs/base/parts/request/common/request'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { URI } from 'vs/base/common/uri'; -import { streamToBuffer } from 'vs/base/common/buffer'; -import { IProductConfiguration } from 'vs/base/common/product'; -import { isString } from 'vs/base/common/types'; -import { CharCode } from 'vs/base/common/charCode'; -import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { isEqualOrParent } from '../../base/common/extpath.js'; +import { getMediaMime } from '../../base/common/mime.js'; +import { isLinux } from '../../base/common/platform.js'; +import { ILogService, LogLevel } from '../../platform/log/common/log.js'; +import { IServerEnvironmentService } from './serverEnvironmentService.js'; +import { extname, dirname, join, normalize, posix, resolve } from '../../base/common/path.js'; +import { FileAccess, connectionTokenCookieName, connectionTokenQueryName, Schemas, builtinExtensionsPath } from '../../base/common/network.js'; +import { generateUuid } from '../../base/common/uuid.js'; +import { IProductService } from '../../platform/product/common/productService.js'; +import { ServerConnectionToken, ServerConnectionTokenType } from './serverConnectionToken.js'; +import { asTextOrError, IRequestService } from '../../platform/request/common/request.js'; +import { IHeaders } from '../../base/parts/request/common/request.js'; +import { CancellationToken } from '../../base/common/cancellation.js'; +import { URI } from '../../base/common/uri.js'; +import { streamToBuffer } from '../../base/common/buffer.js'; +import { IProductConfiguration } from '../../base/common/product.js'; +import { isString, Mutable } from '../../base/common/types.js'; +import { CharCode } from '../../base/common/charCode.js'; +import { IExtensionManifest } from '../../platform/extensions/common/extensions.js'; +import { ICSSDevelopmentService } from '../../platform/cssDev/node/cssDevService.js'; const textMimeType: { [ext: string]: string | undefined } = { '.html': 'text/html', @@ -39,6 +39,10 @@ const textMimeType: { [ext: string]: string | undefined } = { '.svg': 'image/svg+xml', }; +const enum ServiceName { + SAGEMAKER_UNIFIED_STUDIO = 'SageMakerUnifiedStudio', +} + /** * Return an error to the client. */ @@ -56,7 +60,7 @@ export const enum CacheControl { */ export async function serveFile(filePath: string, cacheControl: CacheControl, logService: ILogService, req: http.IncomingMessage, res: http.ServerResponse, responseHeaders: Record): Promise { try { - const stat = await Promises.stat(filePath); // throws an error if file doesn't exist + const stat = await promises.stat(filePath); // throws an error if file doesn't exist if (cacheControl === CacheControl.ETAG) { // Check if file modified since @@ -94,57 +98,57 @@ export async function serveFile(filePath: string, cacheControl: CacheControl, lo const APP_ROOT = dirname(FileAccess.asFileUri('').fsPath); +const STATIC_PATH = `/static`; +const CALLBACK_PATH = `/callback`; +const WEB_EXTENSION_PATH = `/web-extension-resource`; +const IDLE_EXTENSION_PATH = `/api/idle`; +const POST_STARTUP_SCRIPT_PATH = `/api/poststartup`; + export class WebClientServer { private readonly _webExtensionResourceUrlTemplate: URI | undefined; - private readonly _staticRoute: string; - private readonly _callbackRoute: string; - private readonly _webExtensionRoute: string; - private readonly _idleRoute: string; - constructor( private readonly _connectionToken: ServerConnectionToken, private readonly _basePath: string, - readonly serverRootPath: string, + private readonly _productPath: string, @IServerEnvironmentService private readonly _environmentService: IServerEnvironmentService, @ILogService private readonly _logService: ILogService, @IRequestService private readonly _requestService: IRequestService, @IProductService private readonly _productService: IProductService, + @ICSSDevelopmentService private readonly _cssDevService: ICSSDevelopmentService ) { this._webExtensionResourceUrlTemplate = this._productService.extensionsGallery?.resourceUrlTemplate ? URI.parse(this._productService.extensionsGallery.resourceUrlTemplate) : undefined; - - this._staticRoute = `${serverRootPath}/static`; - this._callbackRoute = `${serverRootPath}/callback`; - this._webExtensionRoute = `${serverRootPath}/web-extension-resource`; - this._idleRoute = '/api/idle'; } /** * Handle web resources (i.e. only needed by the web client). * **NOTE**: This method is only invoked when the server has web bits. * **NOTE**: This method is only invoked after the connection token has been validated. + * @param parsedUrl The URL to handle, including base and product path + * @param pathname The pathname of the URL, without base and product path */ - async handle(req: http.IncomingMessage, res: http.ServerResponse, parsedUrl: url.UrlWithParsedQuery): Promise { + async handle(req: http.IncomingMessage, res: http.ServerResponse, parsedUrl: url.UrlWithParsedQuery, pathname: string): Promise { try { - const pathname = parsedUrl.pathname!; - - if (pathname.startsWith(this._staticRoute) && pathname.charCodeAt(this._staticRoute.length) === CharCode.Slash) { - return this._handleStatic(req, res, parsedUrl); + if (pathname.startsWith(STATIC_PATH) && pathname.charCodeAt(STATIC_PATH.length) === CharCode.Slash) { + return this._handleStatic(req, res, pathname.substring(STATIC_PATH.length)); } - if (pathname === this._basePath) { + if (pathname === '/') { return this._handleRoot(req, res, parsedUrl); } - if (pathname === this._idleRoute) { - return this._handleIdle(req, res); - } - if (pathname === this._callbackRoute) { + if (pathname === CALLBACK_PATH) { // callback support return this._handleCallback(res); } - if (pathname.startsWith(this._webExtensionRoute) && pathname.charCodeAt(this._webExtensionRoute.length) === CharCode.Slash) { + if (pathname === IDLE_EXTENSION_PATH) { + return this._handleIdle(req, res); + } + if (pathname === POST_STARTUP_SCRIPT_PATH) { + return this._handlePostStartupScriptInvocation(req, res); + } + if (pathname.startsWith(WEB_EXTENSION_PATH) && pathname.charCodeAt(WEB_EXTENSION_PATH.length) === CharCode.Slash) { // extension resource support - return this._handleWebExtensionResource(req, res, parsedUrl); + return this._handleWebExtensionResource(req, res, pathname.substring(WEB_EXTENSION_PATH.length)); } return serveError(req, res, 404, 'Not found.'); @@ -157,15 +161,15 @@ export class WebClientServer { } /** * Handle HTTP requests for /static/* + * @param resourcePath The path after /static/ */ - private async _handleStatic(req: http.IncomingMessage, res: http.ServerResponse, parsedUrl: url.UrlWithParsedQuery): Promise { + private async _handleStatic(req: http.IncomingMessage, res: http.ServerResponse, resourcePath: string): Promise { const headers: Record = Object.create(null); // Strip the this._staticRoute from the path - const normalizedPathname = decodeURIComponent(parsedUrl.pathname!); // support paths that are uri-encoded (e.g. spaces => %20) - const relativeFilePath = normalizedPathname.substring(this._staticRoute.length + 1); + const normalizedPathname = decodeURIComponent(resourcePath); // support paths that are uri-encoded (e.g. spaces => %20) - const filePath = join(APP_ROOT, relativeFilePath); // join also normalizes the path + const filePath = join(APP_ROOT, normalizedPathname); // join also normalizes the path if (!isEqualOrParent(filePath, APP_ROOT, !isLinux)) { return serveError(req, res, 400, `Bad request.`); } @@ -180,15 +184,15 @@ export class WebClientServer { /** * Handle extension resources + * @param resourcePath The path after /web-extension-resource/ */ - private async _handleWebExtensionResource(req: http.IncomingMessage, res: http.ServerResponse, parsedUrl: url.UrlWithParsedQuery): Promise { + private async _handleWebExtensionResource(req: http.IncomingMessage, res: http.ServerResponse, resourcePath: string): Promise { if (!this._webExtensionResourceUrlTemplate) { return serveError(req, res, 500, 'No extension gallery service configured.'); } - // Strip `/web-extension-resource/` from the path - const normalizedPathname = decodeURIComponent(parsedUrl.pathname!); // support paths that are uri-encoded (e.g. spaces => %20) - const path = normalize(normalizedPathname.substring(this._webExtensionRoute.length + 1)); + const normalizedPathname = decodeURIComponent(resourcePath); // support paths that are uri-encoded (e.g. spaces => %20) + const path = normalize(normalizedPathname); const uri = URI.parse(path).with({ scheme: this._webExtensionResourceUrlTemplate.scheme, authority: path.substring(0, path.indexOf('/')), @@ -228,7 +232,7 @@ export class WebClientServer { return serveError(req, res, status, text || `Request failed with status ${status}`); } - const responseHeaders: Record = Object.create(null); + const responseHeaders: Record = Object.create(null); const setResponseHeader = (header: string) => { const value = context.res.headers[header]; if (value) { @@ -249,6 +253,14 @@ export class WebClientServer { */ private async _handleRoot(req: http.IncomingMessage, res: http.ServerResponse, parsedUrl: url.UrlWithParsedQuery): Promise { + const getFirstHeader = (headerName: string) => { + const val = req.headers[headerName]; + return Array.isArray(val) ? val[0] : val; + }; + + // Prefix routes with basePath for clients + const basePath = getFirstHeader('x-forwarded-prefix') || this._basePath; + const queryConnectionToken = parsedUrl.query[connectionTokenQueryName]; if (typeof queryConnectionToken === 'string') { // We got a connection token as a query parameter. @@ -269,27 +281,35 @@ export class WebClientServer { newQuery[key] = parsedUrl.query[key]; } } - const newLocation = url.format({ pathname: parsedUrl.pathname, query: newQuery }); + const newLocation = url.format({ pathname: basePath, query: newQuery }); responseHeaders['Location'] = newLocation; res.writeHead(302, responseHeaders); return void res.end(); } - const getFirstHeader = (headerName: string) => { - const val = req.headers[headerName]; - return Array.isArray(val) ? val[0] : val; + const replacePort = (host: string, port: string) => { + const index = host?.indexOf(':'); + if (index !== -1) { + host = host?.substring(0, index); + } + host += `:${port}`; + return host; }; const useTestResolver = (!this._environmentService.isBuilt && this._environmentService.args['use-test-resolver']); - const remoteAuthority = ( + let remoteAuthority = ( useTestResolver ? 'test+test' - : (getFirstHeader('x-original-host') || getFirstHeader('x-forwarded-host') || req.headers.host || window.location.host) + : (getFirstHeader('x-original-host') || getFirstHeader('x-forwarded-host') || req.headers.host) ); if (!remoteAuthority) { return serveError(req, res, 400, `Bad request.`); } + const forwardedPort = getFirstHeader('x-forwarded-port'); + if (forwardedPort) { + remoteAuthority = replacePort(remoteAuthority, forwardedPort); + } function asJSON(value: unknown): string { return JSON.stringify(value).replace(/"/g, '"'); @@ -302,9 +322,22 @@ export class WebClientServer { _wrapWebWorkerExtHostInIframe = false; } - const resolveWorkspaceURI = (defaultLocation?: string) => defaultLocation && URI.file(path.resolve(defaultLocation)).with({ scheme: Schemas.vscodeRemote, authority: remoteAuthority }); + if (this._logService.getLevel() === LogLevel.Trace) { + ['x-original-host', 'x-forwarded-host', 'x-forwarded-port', 'host'].forEach(header => { + const value = getFirstHeader(header); + if (value) { + this._logService.trace(`[WebClientServer] ${header}: ${value}`); + } + }); + this._logService.trace(`[WebClientServer] Request URL: ${req.url}, basePath: ${basePath}, remoteAuthority: ${remoteAuthority}`); + } + + const staticRoute = posix.join(basePath, this._productPath, STATIC_PATH); + const callbackRoute = posix.join(basePath, this._productPath, CALLBACK_PATH); - const filePath = FileAccess.asFileUri(this._environmentService.isBuilt ? 'vs/code/browser/workbench/workbench.html' : 'vs/code/browser/workbench/workbench-dev.html').fsPath; + const resolveWorkspaceURI = (defaultLocation?: string) => defaultLocation && URI.file(resolve(defaultLocation)).with({ scheme: Schemas.vscodeRemote, authority: remoteAuthority }); + + const filePath = FileAccess.asFileUri(`vs/code/browser/workbench/workbench${this._environmentService.isBuilt ? '' : '-dev'}.html`).fsPath; const authSessionInfo = !this._environmentService.isBuilt && this._environmentService.args['github-auth'] ? { id: generateUuid(), providerId: 'github', @@ -312,34 +345,28 @@ export class WebClientServer { scopes: [['user:email'], ['repo']] } : undefined; - const basePath: string = this._environmentService.args['base-path'] || "/" - const base = relativeRoot(basePath) - const vscodeBase = relativePath(basePath) - - const productConfiguration = { - rootEndpoint: base, + const productConfiguration: Partial> = { embedderIdentifier: 'server-distro', - extensionsGallery: this._webExtensionResourceUrlTemplate && this._productService.extensionsGallery ? { - ...this._productService.extensionsGallery, - resourceUrlTemplate: this._webExtensionResourceUrlTemplate.with({ - scheme: 'http', - authority: remoteAuthority, - path: `${this._webExtensionRoute}/${this._webExtensionResourceUrlTemplate.authority}${this._webExtensionResourceUrlTemplate.path}` - }).toString(true) - } : undefined - } satisfies Partial; + extensionsGallery: this._productService.extensionsGallery, + }; + + const proposedApi = this._environmentService.args['enable-proposed-api']; + if (proposedApi?.length) { + productConfiguration.extensionsEnabledWithApiProposalVersion ??= []; + productConfiguration.extensionsEnabledWithApiProposalVersion.push(...proposedApi); + } if (!this._environmentService.isBuilt) { try { - const productOverrides = JSON.parse((await Promises.readFile(join(APP_ROOT, 'product.overrides.json'))).toString()); + const productOverrides = JSON.parse((await promises.readFile(join(APP_ROOT, 'product.overrides.json'))).toString()); Object.assign(productConfiguration, productOverrides); } catch (err) {/* Ignore Error */ } } const workbenchWebConfiguration = { remoteAuthority, - serverBasePath: this._basePath, - webviewEndpoint: vscodeBase + this._staticRoute + '/out/vs/workbench/contrib/webview/browser/pre', + serverBasePath: basePath, + webviewEndpoint: staticRoute + '/out/vs/workbench/contrib/webview/browser/pre', userDataPath: this._environmentService.userDataPath, _wrapWebWorkerExtHostInIframe, developmentOptions: { enableSmokeTestDriver: this._environmentService.args['enable-smoke-test-driver'] ? true : undefined, logLevel: this._logService.getLevel() }, @@ -348,23 +375,42 @@ export class WebClientServer { folderUri: resolveWorkspaceURI(this._environmentService.args['default-folder']), workspaceUri: resolveWorkspaceURI(this._environmentService.args['default-workspace']), productConfiguration, - callbackRoute: this._callbackRoute + callbackRoute: callbackRoute }; - const nlsBaseUrl = this._productService.extensionsGallery?.nlsBaseUrl; + const cookies = cookie.parse(req.headers.cookie || ''); + const locale = cookies['vscode.nls.locale'] || req.headers['accept-language']?.split(',')[0]?.toLowerCase() || 'en'; + let WORKBENCH_NLS_BASE_URL: string | undefined; + let WORKBENCH_NLS_URL: string; + if (!locale.startsWith('en') && this._productService.nlsCoreBaseUrl) { + WORKBENCH_NLS_BASE_URL = this._productService.nlsCoreBaseUrl; + WORKBENCH_NLS_URL = `${WORKBENCH_NLS_BASE_URL}${this._productService.commit}/${this._productService.version}/${locale}/nls.messages.js`; + } else { + WORKBENCH_NLS_URL = ''; // fallback will apply + } + const values: { [key: string]: string } = { WORKBENCH_WEB_CONFIGURATION: asJSON(workbenchWebConfiguration), WORKBENCH_AUTH_SESSION: authSessionInfo ? asJSON(authSessionInfo) : '', - WORKBENCH_WEB_BASE_URL: vscodeBase + this._staticRoute, - WORKBENCH_NLS_BASE_URL: vscodeBase + (nlsBaseUrl ? `${nlsBaseUrl}${!nlsBaseUrl.endsWith('/') ? '/' : ''}${this._productService.commit}/${this._productService.version}/` : ''), - BASE: base, - VS_BASE: vscodeBase, + WORKBENCH_WEB_BASE_URL: staticRoute, + WORKBENCH_NLS_URL, + WORKBENCH_NLS_FALLBACK_URL: `${staticRoute}/out/nls.messages.js` }; + // DEV --------------------------------------------------------------------------------------- + // DEV: This is for development and enables loading CSS via import-statements via import-maps. + // DEV: The server needs to send along all CSS modules so that the client can construct the + // DEV: import-map. + // DEV --------------------------------------------------------------------------------------- + if (this._cssDevService.isEnabled) { + const cssModules = await this._cssDevService.getCssModules(); + values['WORKBENCH_DEV_CSS_MODULES'] = JSON.stringify(cssModules); + } + if (useTestResolver) { const bundledExtensions: { extensionPath: string; packageJSON: IExtensionManifest }[] = []; for (const extensionPath of ['vscode-test-resolver', 'github-authentication']) { - const packageJSON = JSON.parse((await Promises.readFile(FileAccess.asFileUri(`${builtinExtensionsPath}/${extensionPath}/package.json`).fsPath)).toString()); + const packageJSON = JSON.parse((await promises.readFile(FileAccess.asFileUri(`${builtinExtensionsPath}/${extensionPath}/package.json`).fsPath)).toString()); bundledExtensions.push({ extensionPath, packageJSON }); } values['WORKBENCH_BUILTIN_EXTENSIONS'] = asJSON(bundledExtensions); @@ -372,25 +418,25 @@ export class WebClientServer { let data; try { - const workbenchTemplate = (await Promises.readFile(filePath)).toString(); + const workbenchTemplate = (await promises.readFile(filePath)).toString(); data = workbenchTemplate.replace(/\{\{([^}]+)\}\}/g, (_, key) => values[key] ?? 'undefined'); } catch (e) { res.writeHead(404, { 'Content-Type': 'text/plain' }); return void res.end('Not found'); } - const webWorkerExtensionHostIframeScriptSHA = 'sha256-75NYUUvf+5++1WbfCZOV3PSWxBhONpaxwx+mkOFRv/Y='; + const webWorkerExtensionHostIframeScriptSHA = 'sha256-2Q+j4hfT09+1+imS46J2YlkCtHWQt0/BE79PXjJ0ZJ8='; const cspDirectives = [ 'default-src \'self\';', 'img-src \'self\' https: data: blob:;', 'media-src \'self\';', - `script-src 'self' 'unsafe-eval' ${this._getScriptCspHashes(data).join(' ')} '${webWorkerExtensionHostIframeScriptSHA}' ${useTestResolver ? '' : `http://${remoteAuthority}`};`, // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html + `script-src 'self' 'unsafe-eval' ${WORKBENCH_NLS_BASE_URL ?? ''} blob: 'nonce-1nline-m4p' ${this._getScriptCspHashes(data).join(' ')} '${webWorkerExtensionHostIframeScriptSHA}' 'sha256-/r7rqQ+yrxt57sxLuQ6AMYcy/lUpvAIzHjIJt/OeLWU=' ${useTestResolver ? '' : `http://${remoteAuthority}`};`, // the sha is the same as in src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html 'child-src \'self\';', `frame-src 'self' https://*.vscode-cdn.net data:;`, 'worker-src \'self\' data: blob:;', 'style-src \'self\' \'unsafe-inline\';', - 'connect-src \'self\' ws: wss: https:;', + 'connect-src \'self\' ws: wss: https://main.vscode-cdn.net http://localhost:* https://localhost:* https://login.microsoftonline.com/ https://update.code.visualstudio.com https://*.vscode-unpkg.net/ https://default.exp-tas.com/vscode/ab https://vscode-sync.trafficmanager.net https://vscode-sync-insiders.trafficmanager.net https://*.gallerycdn.vsassets.io https://marketplace.visualstudio.com https://openvsxorg.blob.core.windows.net https://az764295.vo.msecnd.net https://code.visualstudio.com https://*.gallery.vsassets.io https://*.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com https://*.servicebus.windows.net/ https://vscode.blob.core.windows.net https://vscode.search.windows.net https://vsmarketplacebadges.dev https://vscode.download.prss.microsoft.com https://download.visualstudio.microsoft.com https://*.vscode-unpkg.net https://open-vsx.org;', 'font-src \'self\' blob:;', 'manifest-src \'self\';' ].join(' '); @@ -441,7 +487,7 @@ export class WebClientServer { */ private async _handleCallback(res: http.ServerResponse): Promise { const filePath = FileAccess.asFileUri('vs/code/browser/workbench/callback.html').fsPath; - const data = (await Promises.readFile(filePath)).toString(); + const data = (await promises.readFile(filePath)).toString(); const cspDirectives = [ 'default-src \'self\';', 'img-src \'self\' https: data: blob:;', @@ -459,90 +505,62 @@ export class WebClientServer { } /** - * Handles API requests to retrieve the last activity timestamp. - */ - private async _handleIdle(req: http.IncomingMessage, res: http.ServerResponse): Promise { - try { - const homeDirectory = process.env.HOME || process.env.USERPROFILE; - if (!homeDirectory) { - throw new Error('Home directory not found'); + * Handles API requests to run the post-startup script in SMD. + */ + private async _handlePostStartupScriptInvocation(req: http.IncomingMessage, res: http.ServerResponse): Promise { + const postStartupScriptPath = '/etc/sagemaker-ui/sagemaker_ui_post_startup.sh' + const logPath = '/var/log/apps/post_startup_default.log'; + const logStream = fs.createWriteStream(logPath, { flags: 'a' }); + + // Only trigger post-startup script invocation for SageMakerUnifiedStudio app. + if (process.env['SERVICE_NAME'] != ServiceName.SAGEMAKER_UNIFIED_STUDIO) { + return serveError(req, res, 403, 'Forbidden'); + } else { + //If postStartupScriptFile doesn't exist, it will throw FileNotFoundError (404) + //If exists, it will start the execution and add the execution logs in logFile. + try { + if (fs.existsSync(postStartupScriptPath)) { + // Adding 0o755 to make script file executable + fs.chmodSync(postStartupScriptPath, 0o755); + + const subprocess = spawn('bash', [`${postStartupScriptPath}`], { cwd: '/' }); + subprocess.stdout.pipe(logStream); + subprocess.stderr.pipe(logStream); + + res.statusCode = 200; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({ 'success': 'true' })); + } else { + serveError(req, res, 500, 'Poststartup script file not found at ' + postStartupScriptPath); + } + } catch (error) { + serveError(req, res, 500, error.message); } - - const idleFilePath = path.join(homeDirectory, '.code-editor-last-active-timestamp'); - const data = await readFile(idleFilePath, 'utf8'); - - res.statusCode = 200; - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify({ lastActiveTimestamp: data })); - } catch (error) { - serveError(req, res, 500, error.message) } } -} -/** - * Remove extra slashes in a URL. - * - * This is meant to fill the job of `path.join` so you can concatenate paths and - * then normalize out any extra slashes. - * - * If you are using `path.join` you do not need this but note that `path` is for - * file system paths, not URLs. - */ -export const normalizeUrlPath = (url: string, keepTrailing = false): string => { - return url.replace(/\/\/+/g, "/").replace(/\/+$/, keepTrailing ? "/" : "") -} - -/** - * Get the relative path that will get us to the root of the page. For each - * slash we need to go up a directory. Will not have a trailing slash. - * - * For example: - * - * / => . - * /foo => . - * /foo/ => ./.. - * /foo/bar => ./.. - * /foo/bar/ => ./../.. - * - * All paths must be relative in order to work behind a reverse proxy since we - * we do not know the base path. Anything that needs to be absolute (for - * example cookies) must get the base path from the frontend. - * - * All relative paths must be prefixed with the relative root to ensure they - * work no matter the depth at which they happen to appear. - * - * For Express `req.originalUrl` should be used as they remove the base from the - * standard `url` property making it impossible to get the true depth. - */ -export const relativeRoot = (originalUrl: string): string => { - const depth = (originalUrl.split("?", 1)[0].match(/\//g) || []).length - return normalizeUrlPath("./" + (depth > 1 ? "../".repeat(depth - 1) : "")) -} - -/** - * Get the relative path to the current resource. - * - * For example: - * - * / => . - * /foo => ./foo - * /foo/ => . - * /foo/bar => ./bar - * /foo/bar/ => . - */ -export const relativePath = (originalUrl: string): string => { - const parts = originalUrl.split("?", 1)[0].split("/") - return normalizeUrlPath("./" + parts[parts.length - 1]) -} - -/** - * code-server serves Code using Express. Express removes the base from the url - * and puts the original in `originalUrl` so we must use this to get the correct - * depth. Code is not aware it is behind Express so the types do not match. We - * may want to continue moving code into Code and eventually remove the Express - * wrapper or move the web server back into code-server. - */ -export const getOriginalUrl = (req: http.IncomingMessage): string => { - return (req as any).originalUrl || req.url + /** + * Handles API requests to retrieve the last activity timestamp. + */ + private async _handleIdle(req: http.IncomingMessage, res: http.ServerResponse): Promise { + try { + const tmpDirectory = '/tmp/' + const idleFilePath = join(tmpDirectory, '.sagemaker-last-active-timestamp'); + + // If idle shutdown file does not exist, this indicates the app UI may never been opened + // Create the initial metadata file + if (!existsSync(idleFilePath)) { + const timestamp = new Date().toISOString(); + writeFileSync(idleFilePath, timestamp); + } + + const data = await promises.readFile(idleFilePath, 'utf8'); + + res.statusCode = 200; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({ lastActiveTimestamp: data })); + } catch (error) { + serveError(req, res, 500, error.message) + } + } } diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 0ff4dca2f..43dc8077e 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -258,7 +258,12 @@ Registry.as(ConfigurationExtensions.Configuration) type: 'boolean', description: localize('extensionsInQuickAccess', "When enabled, extensions can be searched for via Quick Access and report issues from there."), default: true - } + }, + 'extensions.openNotebookData': { + type: 'object', + scope: ConfigurationScope.APPLICATION, + default: {}, + }, } }); diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index fc174208e..0c77eb25b 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -3,81 +3,84 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/extensionActions'; -import { localize, localize2 } from 'vs/nls'; -import { IAction, Action, Separator, SubmenuAction } from 'vs/base/common/actions'; -import { Delayer, Promises, Throttler } from 'vs/base/common/async'; -import * as DOM from 'vs/base/browser/dom'; -import { Emitter, Event } from 'vs/base/common/event'; -import * as json from 'vs/base/common/json'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { disposeIfDisposable } from 'vs/base/common/lifecycle'; -import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, IExtensionContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP, UPDATE_ACTIONS_GROUP, AutoUpdateConfigurationKey, AutoUpdateConfigurationValue, ExtensionEditorTab, ExtensionRuntimeActionType, IExtensionArg } from 'vs/workbench/contrib/extensions/common/extensions'; -import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; -import { IGalleryExtension, IExtensionGalleryService, ILocalExtension, InstallOptions, InstallOperation, TargetPlatformToString, ExtensionManagementErrorCode } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { ExtensionRecommendationReason, IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; -import { areSameExtensions, getExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { ExtensionType, ExtensionIdentifier, IExtensionDescription, IExtensionManifest, isLanguagePackExtension, getWorkspaceSupportTypeMessage, TargetPlatform, isApplicationScopedExtension } from 'vs/platform/extensions/common/extensions'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IFileService, IFileContent } from 'vs/platform/files/common/files'; -import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { IExtensionService, toExtension, toExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; -import { URI } from 'vs/base/common/uri'; -import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; -import { ThemeIcon } from 'vs/base/common/themables'; -import { buttonBackground, buttonForeground, buttonHoverBackground, registerColor, editorWarningForeground, editorInfoForeground, editorErrorForeground, buttonSeparator } from 'vs/platform/theme/common/colorRegistry'; -import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; -import { ITextEditorSelection } from 'vs/platform/editor/common/editor'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { MenuId, IMenuService, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; -import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; -import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IQuickPickItem, IQuickInputService, QuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { alert } from 'vs/base/browser/ui/aria/aria'; -import { IWorkbenchThemeService, IWorkbenchTheme, IWorkbenchColorTheme, IWorkbenchFileIconTheme, IWorkbenchProductIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { ILabelService } from 'vs/platform/label/common/label'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { IDialogService, IPromptButton } from 'vs/platform/dialogs/common/dialogs'; -import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { IActionViewItemOptions, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; -import { EXTENSIONS_CONFIG, IExtensionsConfigContent } from 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig'; -import { getErrorMessage, isCancellationError } from 'vs/base/common/errors'; -import { IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; -import { ActionWithDropdownActionViewItem, IActionWithDropdownActionViewItemOptions } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; -import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; -import { ILogService } from 'vs/platform/log/common/log'; -import { errorIcon, infoIcon, manageExtensionIcon, syncEnabledIcon, syncIgnoredIcon, trustIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; -import { isIOS, isWeb, language } from 'vs/base/common/platform'; -import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; -import { IWorkspaceTrustEnablementService, IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; -import { isVirtualWorkspace } from 'vs/platform/workspace/common/virtualWorkspace'; -import { escapeMarkdownSyntaxTokens, IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; -import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; -import { ViewContainerLocation } from 'vs/workbench/common/views'; -import { fromNow } from 'vs/base/common/date'; -import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; -import { getLocale } from 'vs/platform/languagePacks/common/languagePacks'; -import { ILocaleService } from 'vs/workbench/services/localization/common/locale'; -import { isString } from 'vs/base/common/types'; -import { showWindowLogActionId } from 'vs/workbench/services/log/common/logConstants'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { Extensions, IExtensionFeaturesManagementService, IExtensionFeaturesRegistry } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IUpdateService } from 'vs/platform/update/common/update'; +import './media/extensionActions.css'; +import { localize, localize2 } from '../../../../nls.js'; +import { IAction, Action, Separator, SubmenuAction, IActionChangeEvent } from '../../../../base/common/actions.js'; +import { Delayer, Promises, Throttler } from '../../../../base/common/async.js'; +import * as DOM from '../../../../base/browser/dom.js'; +import { Emitter, Event } from '../../../../base/common/event.js'; +import * as json from '../../../../base/common/json.js'; +import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; +import { disposeIfDisposable } from '../../../../base/common/lifecycle.js'; +import { IExtension, ExtensionState, IExtensionsWorkbenchService, IExtensionContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP, UPDATE_ACTIONS_GROUP, ExtensionEditorTab, ExtensionRuntimeActionType, IExtensionArg, AutoUpdateConfigurationKey } from '../common/extensions.js'; +import { ExtensionsConfigurationInitialContent } from '../common/extensionsFileTemplate.js'; +import { IGalleryExtension, IExtensionGalleryService, ILocalExtension, InstallOptions, InstallOperation, ExtensionManagementErrorCode, IAllowedExtensionsService, shouldRequireRepositorySignatureFor } from '../../../../platform/extensionManagement/common/extensionManagement.js'; +import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService } from '../../../services/extensionManagement/common/extensionManagement.js'; +import { ExtensionRecommendationReason, IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from '../../../services/extensionRecommendations/common/extensionRecommendations.js'; +import { areSameExtensions, getExtensionId } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js'; +import { ExtensionType, ExtensionIdentifier, IExtensionDescription, IExtensionManifest, isLanguagePackExtension, getWorkspaceSupportTypeMessage, TargetPlatform, isApplicationScopedExtension } from '../../../../platform/extensions/common/extensions.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { IFileService, IFileContent } from '../../../../platform/files/common/files.js'; +import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from '../../../../platform/workspace/common/workspace.js'; +import { IHostService } from '../../../services/host/browser/host.js'; +import { IExtensionService, toExtension, toExtensionDescription } from '../../../services/extensions/common/extensions.js'; +import { URI } from '../../../../base/common/uri.js'; +import { CommandsRegistry, ICommandService } from '../../../../platform/commands/common/commands.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from '../../../../platform/theme/common/themeService.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { buttonBackground, buttonForeground, buttonHoverBackground, registerColor, editorWarningForeground, editorInfoForeground, editorErrorForeground, buttonSeparator } from '../../../../platform/theme/common/colorRegistry.js'; +import { IJSONEditingService } from '../../../services/configuration/common/jsonEditing.js'; +import { ITextEditorSelection } from '../../../../platform/editor/common/editor.js'; +import { ITextModelService } from '../../../../editor/common/services/resolverService.js'; +import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { MenuId, IMenuService, MenuItemAction, SubmenuItemAction } from '../../../../platform/actions/common/actions.js'; +import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from '../../../browser/actions/workspaceCommands.js'; +import { INotificationService, IPromptChoice, Severity } from '../../../../platform/notification/common/notification.js'; +import { IOpenerService } from '../../../../platform/opener/common/opener.js'; +import { IEditorService } from '../../../services/editor/common/editorService.js'; +import { IQuickPickItem, IQuickInputService, QuickPickItem } from '../../../../platform/quickinput/common/quickInput.js'; +import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { alert } from '../../../../base/browser/ui/aria/aria.js'; +import { IWorkbenchThemeService, IWorkbenchTheme, IWorkbenchColorTheme, IWorkbenchFileIconTheme, IWorkbenchProductIconTheme } from '../../../services/themes/common/workbenchThemeService.js'; +import { ILabelService } from '../../../../platform/label/common/label.js'; +import { ITextFileService } from '../../../services/textfile/common/textfiles.js'; +import { IProductService } from '../../../../platform/product/common/productService.js'; +import { IDialogService, IPromptButton } from '../../../../platform/dialogs/common/dialogs.js'; +import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js'; +import { IActionViewItemOptions, ActionViewItem } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; +import { EXTENSIONS_CONFIG, IExtensionsConfigContent } from '../../../services/extensionRecommendations/common/workspaceExtensionsConfig.js'; +import { getErrorMessage, isCancellationError } from '../../../../base/common/errors.js'; +import { IUserDataSyncEnablementService } from '../../../../platform/userDataSync/common/userDataSync.js'; +import { IContextMenuProvider } from '../../../../base/browser/contextmenu.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; +import { errorIcon, infoIcon, manageExtensionIcon, syncEnabledIcon, syncIgnoredIcon, trustIcon, warningIcon } from './extensionsIcons.js'; +import { isIOS, isWeb, language } from '../../../../base/common/platform.js'; +import { IExtensionManifestPropertiesService } from '../../../services/extensions/common/extensionManifestPropertiesService.js'; +import { IWorkspaceTrustEnablementService, IWorkspaceTrustManagementService } from '../../../../platform/workspace/common/workspaceTrust.js'; +import { isVirtualWorkspace } from '../../../../platform/workspace/common/virtualWorkspace.js'; +import { escapeMarkdownSyntaxTokens, IMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js'; +import { fromNow } from '../../../../base/common/date.js'; +import { IPreferencesService } from '../../../services/preferences/common/preferences.js'; +import { getLocale } from '../../../../platform/languagePacks/common/languagePacks.js'; +import { ILocaleService } from '../../../services/localization/common/locale.js'; +import { isString } from '../../../../base/common/types.js'; +import { showWindowLogActionId } from '../../../services/log/common/logConstants.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; +import { Extensions, IExtensionFeaturesManagementService, IExtensionFeaturesRegistry } from '../../../services/extensionManagement/common/extensionFeatures.js'; +import { Registry } from '../../../../platform/registry/common/platform.js'; +import { IUpdateService } from '../../../../platform/update/common/update.js'; +import { ActionWithDropdownActionViewItem, IActionWithDropdownActionViewItemOptions } from '../../../../base/browser/ui/dropdown/dropdownActionViewItem.js'; +import { IAuthenticationUsageService } from '../../../services/authentication/browser/authenticationUsageService.js'; +import { IExtensionGalleryManifestService } from '../../../../platform/extensionManagement/common/extensionGalleryManifest.js'; +import { IWorkbenchIssueService } from '../../issue/common/issue.js'; +import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js'; export class PromptExtensionInstallFailureAction extends Action { constructor( private readonly extension: IExtension, + private readonly options: InstallOptions | undefined, private readonly version: string, private readonly installOperation: InstallOperation, private readonly error: Error, @@ -91,6 +94,7 @@ export class PromptExtensionInstallFailureAction extends Action { @IInstantiationService private readonly instantiationService: IInstantiationService, @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, @IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, + @IWorkbenchIssueService private readonly workbenchIssueService: IWorkbenchIssueService, ) { super('extension.promptExtensionInstallFailure'); } @@ -134,24 +138,73 @@ export class PromptExtensionInstallFailureAction extends Action { return; } - if ([ExtensionManagementErrorCode.Incompatible, ExtensionManagementErrorCode.IncompatibleTargetPlatform, ExtensionManagementErrorCode.Malicious, ExtensionManagementErrorCode.Deprecated].includes(this.error.name)) { + if ([ExtensionManagementErrorCode.Incompatible, ExtensionManagementErrorCode.IncompatibleApi, ExtensionManagementErrorCode.IncompatibleTargetPlatform, ExtensionManagementErrorCode.Malicious, ExtensionManagementErrorCode.Deprecated].includes(this.error.name)) { await this.dialogService.info(getErrorMessage(this.error)); return; } - if (ExtensionManagementErrorCode.Signature === (this.error.name)) { + if (ExtensionManagementErrorCode.PackageNotSigned === (this.error.name)) { await this.dialogService.prompt({ type: 'error', - message: localize('signature verification failed', "{0} cannot verify the '{1}' extension. Are you sure you want to install it?", this.productService.nameLong, this.extension.displayName || this.extension.identifier.id), + message: localize('not signed', "'{0}' is an extension from an unknown source. Are you sure you want to install?", this.extension.displayName), + detail: getErrorMessage(this.error), buttons: [{ label: localize('install anyway', "Install Anyway"), run: () => { - const installAction = this.instantiationService.createInstance(InstallAction, { donotVerifySignature: true }); + const installAction = this.instantiationService.createInstance(InstallAction, { ...this.options, donotVerifySignature: true, }); installAction.extension = this.extension; return installAction.run(); } }], - cancelButton: localize('cancel', "Cancel") + cancelButton: true + }); + return; + } + + if (ExtensionManagementErrorCode.SignatureVerificationFailed === (this.error.name)) { + await this.dialogService.prompt({ + type: 'error', + message: localize('verification failed', "Cannot install '{0}' extension because {1} cannot verify the extension signature", this.extension.displayName, this.productService.nameLong), + detail: getErrorMessage(this.error), + buttons: [{ + label: localize('learn more', "Learn More"), + run: () => this.openerService.open('https://code.visualstudio.com/docs/editor/extension-marketplace#_the-extension-signature-cannot-be-verified-by-vs-code') + }, { + label: localize('install donot verify', "Install Anyway (Don't Verify Signature)"), + run: () => { + const installAction = this.instantiationService.createInstance(InstallAction, { ...this.options, donotVerifySignature: true, }); + installAction.extension = this.extension; + return installAction.run(); + } + }], + cancelButton: true + }); + return; + } + + if (ExtensionManagementErrorCode.SignatureVerificationInternal === (this.error.name)) { + await this.dialogService.prompt({ + type: 'error', + message: localize('verification failed', "Cannot install '{0}' extension because {1} cannot verify the extension signature", this.extension.displayName, this.productService.nameLong), + detail: getErrorMessage(this.error), + buttons: [{ + label: localize('learn more', "Learn More"), + run: () => this.openerService.open('https://code.visualstudio.com/docs/editor/extension-marketplace#_the-extension-signature-cannot-be-verified-by-vs-code') + }, { + label: localize('report issue', "Report Issue"), + run: () => this.workbenchIssueService.openReporter({ + issueTitle: localize('report issue title', "Extension Signature Verification Failed: {0}", this.extension.displayName), + issueBody: localize('report issue body', "Please include following log `F1 > Open View... > Shared` below.\n\n") + }) + }, { + label: localize('install donot verify', "Install Anyway (Don't Verify Signature)"), + run: () => { + const installAction = this.instantiationService.createInstance(InstallAction, { ...this.options, donotVerifySignature: true, }); + installAction.extension = this.extension; + return installAction.run(); + } + }], + cancelButton: true }); return; } @@ -190,9 +243,6 @@ export class PromptExtensionInstallFailureAction extends Action { if (!this.extension.gallery) { return undefined; } - if (!this.productService.extensionsGallery) { - return undefined; - } if (!this.extensionManagementServerService.localExtensionManagementServer && !this.extensionManagementServerService.remoteExtensionManagementServer) { return undefined; } @@ -211,26 +261,68 @@ export class PromptExtensionInstallFailureAction extends Action { if (targetPlatform === TargetPlatform.UNKNOWN) { return undefined; } - return URI.parse(`${this.productService.extensionsGallery.serviceUrl}/publishers/${this.extension.publisher}/vsextensions/${this.extension.name}/${this.version}/vspackage${targetPlatform !== TargetPlatform.UNDEFINED ? `?targetPlatform=${targetPlatform}` : ''}`); + + const [extension] = await this.galleryService.getExtensions([{ + ...this.extension.identifier, + version: this.version + }], { + targetPlatform + }, CancellationToken.None); + + if (!extension) { + return undefined; + } + return URI.parse(extension.assets.download.uri); } } +export interface IExtensionActionChangeEvent extends IActionChangeEvent { + readonly hidden?: boolean; + readonly menuActions?: IAction[]; +} + export abstract class ExtensionAction extends Action implements IExtensionContainer { + + protected override _onDidChange = this._register(new Emitter()); + override readonly onDidChange = this._onDidChange.event; + static readonly EXTENSION_ACTION_CLASS = 'extension-action'; static readonly TEXT_ACTION_CLASS = `${ExtensionAction.EXTENSION_ACTION_CLASS} text`; static readonly LABEL_ACTION_CLASS = `${ExtensionAction.EXTENSION_ACTION_CLASS} label`; + static readonly PROMINENT_LABEL_ACTION_CLASS = `${ExtensionAction.LABEL_ACTION_CLASS} prominent`; static readonly ICON_ACTION_CLASS = `${ExtensionAction.EXTENSION_ACTION_CLASS} icon`; + private _extension: IExtension | null = null; get extension(): IExtension | null { return this._extension; } set extension(extension: IExtension | null) { this._extension = extension; this.update(); } + + private _hidden: boolean = false; + get hidden(): boolean { return this._hidden; } + set hidden(hidden: boolean) { + if (this._hidden !== hidden) { + this._hidden = hidden; + this._onDidChange.fire({ hidden }); + } + } + + protected override _setEnabled(value: boolean): void { + super._setEnabled(value); + if (this.hideOnDisabled) { + this.hidden = !value; + } + } + + protected hideOnDisabled: boolean = true; + abstract update(): void; } -export class ActionWithDropDownAction extends ExtensionAction { +export class ButtonWithDropDownExtensionAction extends ExtensionAction { - private action: IAction | undefined; + private primaryAction: IAction | undefined; + readonly menuActionClassNames: string[] = []; private _menuActions: IAction[] = []; get menuActions(): IAction[] { return [...this._menuActions]; } @@ -246,10 +338,14 @@ export class ActionWithDropDownAction extends ExtensionAction { protected readonly extensionActions: ExtensionAction[]; constructor( - id: string, label: string, + id: string, + clazz: string, private readonly actionsGroups: ExtensionAction[][], ) { - super(id, label); + clazz = `${clazz} action-dropdown`; + super(id, undefined, clazz); + this.menuActionClassNames = clazz.split(' '); + this.hideOnDisabled = false; this.extensionActions = actionsGroups.flat(); this.update(); this._register(Event.any(...this.extensionActions.map(a => a.onDidChange))(() => this.update(true))); @@ -261,36 +357,35 @@ export class ActionWithDropDownAction extends ExtensionAction { this.extensionActions.forEach(a => a.update()); } - const enabledActionsGroups = this.actionsGroups.map(actionsGroup => actionsGroup.filter(a => a.enabled)); + const actionsGroups = this.actionsGroups.map(actionsGroup => actionsGroup.filter(a => !a.hidden)); let actions: IAction[] = []; - for (const enabledActions of enabledActionsGroups) { - if (enabledActions.length) { - actions = [...actions, ...enabledActions, new Separator()]; + for (const visibleActions of actionsGroups) { + if (visibleActions.length) { + actions = [...actions, ...visibleActions, new Separator()]; } } actions = actions.length ? actions.slice(0, actions.length - 1) : actions; - this.action = actions[0]; + this.primaryAction = actions[0]; this._menuActions = actions.length > 1 ? actions : []; + this._onDidChange.fire({ menuActions: this._menuActions }); - this.enabled = !!this.action; - if (this.action) { - this.label = this.getLabel(this.action as ExtensionAction); - this.tooltip = this.action.tooltip; - } - - let clazz = (this.action || this.extensionActions[0])?.class || ''; - clazz = clazz ? `${clazz} action-dropdown` : 'action-dropdown'; - if (this._menuActions.length === 0) { - clazz += ' action-dropdown'; + if (this.primaryAction) { + this.hidden = false; + this.enabled = this.primaryAction.enabled; + this.label = this.getLabel(this.primaryAction as ExtensionAction); + this.tooltip = this.primaryAction.tooltip; + } else { + this.hidden = true; + this.enabled = false; } - this.class = clazz; } - override run(): Promise { - const enabledActions = this.extensionActions.filter(a => a.enabled); - return enabledActions[0].run(); + override async run(): Promise { + if (this.enabled) { + await this.primaryAction?.run(); + } } protected getLabel(action: ExtensionAction): string { @@ -298,9 +393,42 @@ export class ActionWithDropDownAction extends ExtensionAction { } } +export class ButtonWithDropdownExtensionActionViewItem extends ActionWithDropdownActionViewItem { + + constructor( + action: ButtonWithDropDownExtensionAction, + options: IActionViewItemOptions & IActionWithDropdownActionViewItemOptions, + contextMenuProvider: IContextMenuProvider + ) { + super(null, action, options, contextMenuProvider); + this._register(action.onDidChange(e => { + if (e.hidden !== undefined || e.menuActions !== undefined) { + this.updateClass(); + } + })); + } + + override render(container: HTMLElement): void { + super.render(container); + this.updateClass(); + } + + protected override updateClass(): void { + super.updateClass(); + if (this.element && this.dropdownMenuActionViewItem?.element) { + this.element.classList.toggle('hide', (this._action).hidden); + const isMenuEmpty = (this._action).menuActions.length === 0; + this.element.classList.toggle('empty', isMenuEmpty); + this.dropdownMenuActionViewItem.element.classList.toggle('hide', isMenuEmpty); + } + } + +} + export class InstallAction extends ExtensionAction { - static readonly Class = `${ExtensionAction.LABEL_ACTION_CLASS} prominent install`; + static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent install`; + private static readonly HIDE = `${this.CLASS} hide`; protected _manifest: IExtensionManifest | null = null; set manifest(manifest: IExtensionManifest | null) { @@ -322,10 +450,14 @@ export class InstallAction extends ExtensionAction { @IPreferencesService private readonly preferencesService: IPreferencesService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @IAllowedExtensionsService private readonly allowedExtensionsService: IAllowedExtensionsService, + @IExtensionGalleryManifestService private readonly extensionGalleryManifestService: IExtensionGalleryManifestService, ) { - super('extensions.install', localize('install', "Install"), InstallAction.Class, false); - this.options = { ...options, isMachineScoped: false }; + super('extensions.install', localize('install', "Install"), InstallAction.CLASS, false); + this.hideOnDisabled = false; + this.options = { isMachineScoped: false, ...options }; this.update(); + this._register(allowedExtensionsService.onDidChangeAllowedExtensionsConfigValue(() => this.update())); this._register(this.labelService.onDidChangeFormatters(() => this.updateLabel(), this)); } @@ -335,17 +467,27 @@ export class InstallAction extends ExtensionAction { protected async computeAndUpdateEnablement(): Promise { this.enabled = false; + this.class = InstallAction.HIDE; + this.hidden = true; if (!this.extension) { return; } if (this.extension.isBuiltin) { return; } - if (this.extensionsWorkbenchService.canSetLanguage(this.extension)) { + if (this.extension.state !== ExtensionState.Uninstalled) { return; } - if (this.extension.state === ExtensionState.Uninstalled && await this.extensionsWorkbenchService.canInstall(this.extension)) { - this.enabled = this.options.installPreReleaseVersion ? this.extension.hasPreReleaseVersion : this.extension.hasReleaseVersion; + if (this.options.installPreReleaseVersion && (!this.extension.hasPreReleaseVersion || this.allowedExtensionsService.isAllowed({ id: this.extension.identifier.id, publisherDisplayName: this.extension.publisherDisplayName, prerelease: true }) !== true)) { + return; + } + if (!this.options.installPreReleaseVersion && !this.extension.hasReleaseVersion) { + return; + } + this.hidden = false; + this.class = InstallAction.CLASS; + if (await this.extensionsWorkbenchService.canInstall(this.extension) === true) { + this.enabled = true; this.updateLabel(); } } @@ -355,6 +497,29 @@ export class InstallAction extends ExtensionAction { return; } + if (this.extension.gallery && !this.extension.gallery.isSigned && shouldRequireRepositorySignatureFor(this.extension.private, await this.extensionGalleryManifestService.getExtensionGalleryManifest())) { + const { result } = await this.dialogService.prompt({ + type: Severity.Warning, + message: localize('not signed', "'{0}' is an extension from an unknown source. Are you sure you want to install?", this.extension.displayName), + detail: localize('not signed detail', "Extension is not signed."), + buttons: [ + { + label: localize('install anyway', "Install Anyway"), + run: () => { + this.options.donotVerifySignature = true; + return true; + } + } + ], + cancelButton: { + run: () => false + } + }); + if (!result) { + return; + } + } + if (this.extension.deprecationInfo) { let detail: string | MarkdownString = localize('deprecated message', "This extension is deprecated as it is no longer being maintained."); enum DeprecationChoice { @@ -473,7 +638,7 @@ export class InstallAction extends ExtensionAction { try { return await this.extensionsWorkbenchService.install(extension, this.options); } catch (error) { - await this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, extension.latestVersion, InstallOperation.Install, error).run(); + await this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, this.options, extension.latestVersion, InstallOperation.Install, error).run(); return undefined; } } @@ -518,7 +683,7 @@ export class InstallAction extends ExtensionAction { } -export class InstallDropdownAction extends ActionWithDropDownAction { +export class InstallDropdownAction extends ButtonWithDropDownExtensionAction { set manifest(manifest: IExtensionManifest | null) { this.extensionActions.forEach(a => (a).manifest = manifest); @@ -527,12 +692,12 @@ export class InstallDropdownAction extends ActionWithDropDownAction { constructor( @IInstantiationService instantiationService: IInstantiationService, - @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, + @IWorkbenchExtensionManagementService extensionManagementService: IWorkbenchExtensionManagementService, ) { - super(`extensions.installActions`, '', [ + super(`extensions.installActions`, InstallAction.CLASS, [ [ - instantiationService.createInstance(InstallAction, { installPreReleaseVersion: extensionsWorkbenchService.preferPreReleases }), - instantiationService.createInstance(InstallAction, { installPreReleaseVersion: !extensionsWorkbenchService.preferPreReleases }), + instantiationService.createInstance(InstallAction, { installPreReleaseVersion: extensionManagementService.preferPreReleases }), + instantiationService.createInstance(InstallAction, { installPreReleaseVersion: !extensionManagementService.preferPreReleases }), ] ]); } @@ -562,8 +727,8 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { protected static readonly INSTALL_LABEL = localize('install', "Install"); protected static readonly INSTALLING_LABEL = localize('installing', "Installing"); - private static readonly Class = `${ExtensionAction.LABEL_ACTION_CLASS} prominent install`; - private static readonly InstallingClass = `${ExtensionAction.LABEL_ACTION_CLASS} install installing`; + private static readonly Class = `${ExtensionAction.LABEL_ACTION_CLASS} prominent install-other-server`; + private static readonly InstallingClass = `${ExtensionAction.LABEL_ACTION_CLASS} install-other-server installing`; updateWhenCounterExtensionChanges: boolean = true; @@ -614,7 +779,7 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { } if (isLanguagePackExtension(this.extension.local.manifest)) { - return true; + return false; } // Prefers to run on UI @@ -721,11 +886,12 @@ export class UninstallAction extends ExtensionAction { static readonly UninstallLabel = localize('uninstallAction', "Uninstall"); private static readonly UninstallingLabel = localize('Uninstalling', "Uninstalling"); - private static readonly UninstallClass = `${ExtensionAction.LABEL_ACTION_CLASS} uninstall`; + static readonly UninstallClass = `${ExtensionAction.LABEL_ACTION_CLASS} uninstall`; private static readonly UnInstallingClass = `${ExtensionAction.LABEL_ACTION_CLASS} uninstall uninstalling`; constructor( @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, @IDialogService private readonly dialogService: IDialogService ) { super('extensions.uninstall', UninstallAction.UninstallLabel, UninstallAction.UninstallClass, false); @@ -747,7 +913,7 @@ export class UninstallAction extends ExtensionAction { return; } - this.label = UninstallAction.UninstallLabel; + this.label = this.extension.local?.isApplicationScoped && this.userDataProfilesService.profiles.length > 1 ? localize('uninstallAll', "Uninstall (All Profiles)") : UninstallAction.UninstallLabel; this.class = UninstallAction.UninstallClass; this.tooltip = UninstallAction.UninstallLabel; @@ -774,28 +940,36 @@ export class UninstallAction extends ExtensionAction { await this.extensionsWorkbenchService.uninstall(this.extension); alert(localize('uninstallExtensionComplete', "Please reload Visual Studio Code to complete the uninstallation of the extension {0}.", this.extension.displayName)); } catch (error) { - this.dialogService.error(getErrorMessage(error)); + if (!isCancellationError(error)) { + this.dialogService.error(getErrorMessage(error)); + } } } } -abstract class AbstractUpdateAction extends ExtensionAction { +export class UpdateAction extends ExtensionAction { - private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} prominent update`; - private static readonly DisabledClass = `${AbstractUpdateAction.EnabledClass} disabled`; + private static readonly EnabledClass = `${this.LABEL_ACTION_CLASS} prominent update`; + private static readonly DisabledClass = `${this.EnabledClass} disabled`; private readonly updateThrottler = new Throttler(); constructor( - id: string, label: string | undefined, - protected readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + private readonly verbose: boolean, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IDialogService private readonly dialogService: IDialogService, + @IOpenerService private readonly openerService: IOpenerService, + @IInstantiationService private readonly instantiationService: IInstantiationService, ) { - super(id, label, AbstractUpdateAction.DisabledClass, false); + super(`extensions.update`, localize('update', "Update"), UpdateAction.DisabledClass, false); this.update(); } update(): void { this.updateThrottler.queue(() => this.computeAndUpdateEnablement()); + if (this.extension) { + this.label = this.verbose ? localize('update to', "Update to v{0}", this.extension.latestVersion) : localize('update', "Update"); + } } private async computeAndUpdateEnablement(): Promise { @@ -813,42 +987,59 @@ abstract class AbstractUpdateAction extends ExtensionAction { const canInstall = await this.extensionsWorkbenchService.canInstall(this.extension); const isInstalled = this.extension.state === ExtensionState.Installed; - this.enabled = canInstall && isInstalled && this.extension.outdated; - this.class = this.enabled ? AbstractUpdateAction.EnabledClass : AbstractUpdateAction.DisabledClass; - } -} - -export class UpdateAction extends AbstractUpdateAction { - - constructor( - private readonly verbose: boolean, - @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, - @IInstantiationService protected readonly instantiationService: IInstantiationService, - ) { - super(`extensions.update`, localize('update', "Update"), extensionsWorkbenchService); - } - - override update(): void { - super.update(); - if (this.extension) { - this.label = this.verbose ? localize('update to', "Update to v{0}", this.extension.latestVersion) : localize('update', "Update"); - } + this.enabled = canInstall === true && isInstalled && this.extension.outdated; + this.class = this.enabled ? UpdateAction.EnabledClass : UpdateAction.DisabledClass; } override async run(): Promise { if (!this.extension) { return; } - alert(localize('updateExtensionStart', "Updating extension {0} to version {1} started.", this.extension.displayName, this.extension.latestVersion)); - return this.install(this.extension); - } - private async install(extension: IExtension): Promise { + const consent = await this.extensionsWorkbenchService.shouldRequireConsentToUpdate(this.extension); + if (consent) { + const { result } = await this.dialogService.prompt<'update' | 'review' | 'cancel'>({ + type: 'warning', + title: localize('updateExtensionConsentTitle', "Update {0} Extension", this.extension.displayName), + message: localize('updateExtensionConsent', "{0}\n\nWould you like to update the extension?", consent), + buttons: [{ + label: localize('update', "Update"), + run: () => 'update' + }, { + label: localize('review', "Review"), + run: () => 'review' + }, { + label: localize('cancel', "Cancel"), + run: () => 'cancel' + }] + }); + if (result === 'cancel') { + return; + } + if (result === 'review') { + if (this.extension.hasChangelog()) { + return this.extensionsWorkbenchService.open(this.extension, { tab: ExtensionEditorTab.Changelog }); + } + if (this.extension.repository) { + return this.openerService.open(this.extension.repository); + } + return this.extensionsWorkbenchService.open(this.extension); + } + } + + const installOptions: InstallOptions = {}; + if (this.extension.local?.source === 'vsix' && this.extension.local.pinned) { + installOptions.pinned = false; + } + if (this.extension.local?.preRelease) { + installOptions.installPreReleaseVersion = true; + } try { - await this.extensionsWorkbenchService.install(extension, extension.local?.preRelease ? { installPreReleaseVersion: true } : undefined); - alert(localize('updateExtensionComplete', "Updating extension {0} to version {1} completed.", extension.displayName, extension.latestVersion)); + alert(localize('updateExtensionStart', "Updating extension {0} to version {1} started.", this.extension.displayName, this.extension.latestVersion)); + await this.extensionsWorkbenchService.install(this.extension, installOptions); + alert(localize('updateExtensionComplete', "Updating extension {0} to version {1} completed.", this.extension.displayName, this.extension.latestVersion)); } catch (err) { - this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, extension.latestVersion, InstallOperation.Update, err).run(); + this.instantiationService.createInstance(PromptExtensionInstallFailureAction, this.extension, installOptions, this.extension.latestVersion, InstallOperation.Update, err).run(); } } } @@ -859,14 +1050,13 @@ export class ToggleAutoUpdateForExtensionAction extends ExtensionAction { static readonly LABEL = localize2('enableAutoUpdateLabel', "Auto Update"); private static readonly EnabledClass = `${ExtensionAction.EXTENSION_ACTION_CLASS} auto-update`; - private static readonly DisabledClass = `${ToggleAutoUpdateForExtensionAction.EnabledClass} hide`; + private static readonly DisabledClass = `${this.EnabledClass} hide`; constructor( - private readonly enableWhenOutdated: boolean, - private readonly enableWhenAutoUpdateValue: AutoUpdateConfigurationValue[], @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, + @IAllowedExtensionsService private readonly allowedExtensionsService: IAllowedExtensionsService, @IConfigurationService configurationService: IConfigurationService, - ) { super(ToggleAutoUpdateForExtensionAction.ID, ToggleAutoUpdateForExtensionAction.LABEL.value, ToggleAutoUpdateForExtensionAction.DisabledClass); this._register(configurationService.onDidChangeConfiguration(e => { @@ -874,6 +1064,7 @@ export class ToggleAutoUpdateForExtensionAction extends ExtensionAction { this.update(); } })); + this._register(allowedExtensionsService.onDidChangeAllowedExtensionsConfigValue(e => this.update())); this.update(); } @@ -886,10 +1077,15 @@ export class ToggleAutoUpdateForExtensionAction extends ExtensionAction { if (this.extension.isBuiltin) { return; } - if (this.enableWhenOutdated && (this.extension.state !== ExtensionState.Installed || !this.extension.outdated)) { + if (this.extension.deprecationInfo?.disallowInstall) { + return; + } + + const extension = this.extension.local ?? this.extension.gallery; + if (extension && this.allowedExtensionsService.isAllowed(extension) !== true) { return; } - if (!this.enableWhenAutoUpdateValue.includes(this.extensionsWorkbenchService.getAutoUpdateValue())) { + if (this.extensionsWorkbenchService.getAutoUpdateValue() === 'onlyEnabledExtensions' && !this.extensionEnablementService.isEnabledEnablementState(this.extension.enablementState)) { return; } this.enabled = true; @@ -944,7 +1140,7 @@ export class ToggleAutoUpdatesForPublisherAction extends ExtensionAction { export class MigrateDeprecatedExtensionAction extends ExtensionAction { private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} migrate`; - private static readonly DisabledClass = `${MigrateDeprecatedExtensionAction.EnabledClass} disabled`; + private static readonly DisabledClass = `${this.EnabledClass} disabled`; constructor( private readonly small: boolean, @@ -987,32 +1183,7 @@ export class MigrateDeprecatedExtensionAction extends ExtensionAction { } } -export class ExtensionActionWithDropdownActionViewItem extends ActionWithDropdownActionViewItem { - - constructor( - action: ActionWithDropDownAction, - options: IActionViewItemOptions & IActionWithDropdownActionViewItemOptions, - contextMenuProvider: IContextMenuProvider - ) { - super(null, action, options, contextMenuProvider); - } - - override render(container: HTMLElement): void { - super.render(container); - this.updateClass(); - } - - protected override updateClass(): void { - super.updateClass(); - if (this.element && this.dropdownMenuActionViewItem && this.dropdownMenuActionViewItem.element) { - this.element.classList.toggle('empty', (this._action).menuActions.length === 0); - this.dropdownMenuActionViewItem.element.classList.toggle('hide', (this._action).menuActions.length === 0); - } - } - -} - -export abstract class ExtensionDropDownAction extends ExtensionAction { +export abstract class DropDownExtensionAction extends ExtensionAction { constructor( id: string, @@ -1024,29 +1195,29 @@ export abstract class ExtensionDropDownAction extends ExtensionAction { super(id, label, cssClass, enabled); } - private _actionViewItem: DropDownMenuActionViewItem | null = null; - createActionViewItem(options: IActionViewItemOptions): DropDownMenuActionViewItem { - this._actionViewItem = this.instantiationService.createInstance(DropDownMenuActionViewItem, this, options); + private _actionViewItem: DropDownExtensionActionViewItem | null = null; + createActionViewItem(options: IActionViewItemOptions): DropDownExtensionActionViewItem { + this._actionViewItem = this.instantiationService.createInstance(DropDownExtensionActionViewItem, this, options); return this._actionViewItem; } - public override run({ actionGroups, disposeActionsOnHide }: { actionGroups: IAction[][]; disposeActionsOnHide: boolean }): Promise { - this._actionViewItem?.showMenu(actionGroups, disposeActionsOnHide); + public override run(actionGroups: IAction[][]): Promise { + this._actionViewItem?.showMenu(actionGroups); return Promise.resolve(); } } -export class DropDownMenuActionViewItem extends ActionViewItem { +export class DropDownExtensionActionViewItem extends ActionViewItem { constructor( - action: ExtensionDropDownAction, + action: IAction, options: IActionViewItemOptions, @IContextMenuService private readonly contextMenuService: IContextMenuService ) { super(null, action, { ...options, icon: true, label: true }); } - public showMenu(menuActionGroups: IAction[][], disposeActionsOnHide: boolean): void { + public showMenu(menuActionGroups: IAction[][]): void { if (this.element) { const actions = this.getActions(menuActionGroups); const elementPosition = DOM.getDomNodePagePosition(this.element); @@ -1055,7 +1226,7 @@ export class DropDownMenuActionViewItem extends ActionViewItem { getAnchor: () => anchor, getActions: () => actions, actionRunner: this.actionRunner, - onHide: () => { if (disposeActionsOnHide) { disposeIfDisposable(actions); } } + onHide: () => disposeIfDisposable(actions) }); } } @@ -1072,10 +1243,13 @@ export class DropDownMenuActionViewItem extends ActionViewItem { async function getContextMenuActionsGroups(extension: IExtension | undefined | null, contextKeyService: IContextKeyService, instantiationService: IInstantiationService): Promise<[string, Array][]> { return instantiationService.invokeFunction(async accessor => { const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService); + const extensionEnablementService = accessor.get(IWorkbenchExtensionEnablementService); const menuService = accessor.get(IMenuService); const extensionRecommendationsService = accessor.get(IExtensionRecommendationsService); const extensionIgnoredRecommendationsService = accessor.get(IExtensionIgnoredRecommendationsService); const workbenchThemeService = accessor.get(IWorkbenchThemeService); + const authenticationUsageService = accessor.get(IAuthenticationUsageService); + const allowedExtensionsService = accessor.get(IAllowedExtensionsService); const cksOverlay: [string, any][] = []; if (extension) { @@ -1084,6 +1258,7 @@ async function getContextMenuActionsGroups(extension: IExtension | undefined | n cksOverlay.push(['isDefaultApplicationScopedExtension', extension.local && isApplicationScopedExtension(extension.local.manifest)]); cksOverlay.push(['isApplicationScopedExtension', extension.local && extension.local.isApplicationScoped]); cksOverlay.push(['isWorkspaceScopedExtension', extension.isWorkspaceScoped]); + cksOverlay.push(['isGalleryExtension', !!extension.identifier.uuid]); if (extension.local) { cksOverlay.push(['extensionSource', extension.local.source]); } @@ -1093,27 +1268,45 @@ async function getContextMenuActionsGroups(extension: IExtension | undefined | n cksOverlay.push(['isExtensionRecommended', !!extensionRecommendationsService.getAllRecommendationsWithReason()[extension.identifier.id.toLowerCase()]]); cksOverlay.push(['isExtensionWorkspaceRecommended', extensionRecommendationsService.getAllRecommendationsWithReason()[extension.identifier.id.toLowerCase()]?.reasonId === ExtensionRecommendationReason.Workspace]); cksOverlay.push(['isUserIgnoredRecommendation', extensionIgnoredRecommendationsService.globalIgnoredRecommendations.some(e => e === extension.identifier.id.toLowerCase())]); - if (extension.state === ExtensionState.Installed) { - cksOverlay.push(['extensionStatus', 'installed']); + cksOverlay.push(['isExtensionPinned', extension.pinned]); + cksOverlay.push(['isExtensionEnabled', extensionEnablementService.isEnabledEnablementState(extension.enablementState)]); + switch (extension.state) { + case ExtensionState.Installing: + cksOverlay.push(['extensionStatus', 'installing']); + break; + case ExtensionState.Installed: + cksOverlay.push(['extensionStatus', 'installed']); + break; + case ExtensionState.Uninstalling: + cksOverlay.push(['extensionStatus', 'uninstalling']); + break; + case ExtensionState.Uninstalled: + cksOverlay.push(['extensionStatus', 'uninstalled']); + break; } cksOverlay.push(['installedExtensionIsPreReleaseVersion', !!extension.local?.isPreReleaseVersion]); cksOverlay.push(['installedExtensionIsOptedToPreRelease', !!extension.local?.preRelease]); cksOverlay.push(['galleryExtensionIsPreReleaseVersion', !!extension.gallery?.properties.isPreReleaseVersion]); cksOverlay.push(['galleryExtensionHasPreReleaseVersion', extension.gallery?.hasPreReleaseVersion]); + cksOverlay.push(['extensionHasPreReleaseVersion', extension.hasPreReleaseVersion]); cksOverlay.push(['extensionHasReleaseVersion', extension.hasReleaseVersion]); + cksOverlay.push(['extensionDisallowInstall', extension.isMalicious || extension.deprecationInfo?.disallowInstall]); + cksOverlay.push(['isExtensionAllowed', allowedExtensionsService.isAllowed({ id: extension.identifier.id, publisherDisplayName: extension.publisherDisplayName }) === true]); + cksOverlay.push(['isPreReleaseExtensionAllowed', allowedExtensionsService.isAllowed({ id: extension.identifier.id, publisherDisplayName: extension.publisherDisplayName, prerelease: true }) === true]); + cksOverlay.push(['extensionIsUnsigned', extension.gallery && !extension.gallery.isSigned]); + cksOverlay.push(['extensionIsPrivate', extension.gallery?.private]); - const [colorThemes, fileIconThemes, productIconThemes] = await Promise.all([workbenchThemeService.getColorThemes(), workbenchThemeService.getFileIconThemes(), workbenchThemeService.getProductIconThemes()]); + const [colorThemes, fileIconThemes, productIconThemes, extensionUsesAuth] = await Promise.all([workbenchThemeService.getColorThemes(), workbenchThemeService.getFileIconThemes(), workbenchThemeService.getProductIconThemes(), authenticationUsageService.extensionUsesAuth(extension.identifier.id.toLowerCase())]); cksOverlay.push(['extensionHasColorThemes', colorThemes.some(theme => isThemeFromExtension(theme, extension))]); cksOverlay.push(['extensionHasFileIconThemes', fileIconThemes.some(theme => isThemeFromExtension(theme, extension))]); cksOverlay.push(['extensionHasProductIconThemes', productIconThemes.some(theme => isThemeFromExtension(theme, extension))]); + cksOverlay.push(['extensionHasAccountPreferences', extensionUsesAuth]); cksOverlay.push(['canSetLanguage', extensionsWorkbenchService.canSetLanguage(extension)]); cksOverlay.push(['isActiveLanguagePackExtension', extension.gallery && language === getLocale(extension.gallery)]); } - const menu = menuService.createMenu(MenuId.ExtensionContext, contextKeyService.createOverlay(cksOverlay)); - const actionsGroups = menu.getActions({ shouldForwardArgs: true }); - menu.dispose(); + const actionsGroups = menuService.getMenuActions(MenuId.ExtensionContext, contextKeyService.createOverlay(cksOverlay), { shouldForwardArgs: true }); return actionsGroups; }); } @@ -1137,12 +1330,12 @@ export async function getContextMenuActions(extension: IExtension | undefined | return toActions(actionsGroups, instantiationService); } -export class ManageExtensionAction extends ExtensionDropDownAction { +export class ManageExtensionAction extends DropDownExtensionAction { static readonly ID = 'extensions.manage'; private static readonly Class = `${ExtensionAction.ICON_ACTION_CLASS} manage ` + ThemeIcon.asClassName(manageExtensionIcon); - private static readonly HideManageExtensionClass = `${ManageExtensionAction.Class} hide`; + private static readonly HideManageExtensionClass = `${this.Class} hide`; constructor( @IInstantiationService instantiationService: IInstantiationService, @@ -1190,7 +1383,7 @@ export class ManageExtensionAction extends ExtensionDropDownAction { } groups.push([ ...(installActions.length ? installActions : []), - this.instantiationService.createInstance(InstallAnotherVersionAction), + this.instantiationService.createInstance(InstallAnotherVersionAction, this.extension, false), this.instantiationService.createInstance(UninstallAction), ]); @@ -1207,7 +1400,7 @@ export class ManageExtensionAction extends ExtensionDropDownAction { override async run(): Promise { await this.extensionService.whenInstalledExtensionsRegistered(); - return super.run({ actionGroups: await this.getActionGroups(), disposeActionsOnHide: true }); + return super.run(await this.getActionGroups()); } update(): void { @@ -1221,7 +1414,7 @@ export class ManageExtensionAction extends ExtensionDropDownAction { } } -export class ExtensionEditorManageExtensionAction extends ExtensionDropDownAction { +export class ExtensionEditorManageExtensionAction extends DropDownExtensionAction { constructor( private readonly contextKeyService: IContextKeyService, @@ -1241,7 +1434,7 @@ export class ExtensionEditorManageExtensionAction extends ExtensionDropDownActio extensionAction.extension = this.extension; } })); - return super.run({ actionGroups, disposeActionsOnHide: true }); + return super.run(actionGroups); } } @@ -1255,6 +1448,14 @@ export class MenuItemExtensionAction extends ExtensionAction { super(action.id, action.label); } + override get enabled(): boolean { + return this.action.enabled; + } + + override set enabled(value: boolean) { + this.action.enabled = value; + } + update() { if (!this.extension) { return; @@ -1278,7 +1479,8 @@ export class MenuItemExtensionAction extends ExtensionAction { const extensionArg: IExtensionArg = { id: this.extension.identifier.id, version: this.extension.version, - location: this.extension.local?.location + location: this.extension.local?.location, + galleryLink: this.extension.url }; await this.action.run(id, extensionArg); } @@ -1291,12 +1493,14 @@ export class TogglePreReleaseExtensionAction extends ExtensionAction { static readonly LABEL = localize('togglePreRleaseLabel', "Pre-Release"); private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} pre-release`; - private static readonly DisabledClass = `${TogglePreReleaseExtensionAction.EnabledClass} hide`; + private static readonly DisabledClass = `${this.EnabledClass} hide`; constructor( @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IAllowedExtensionsService private readonly allowedExtensionsService: IAllowedExtensionsService, ) { super(TogglePreReleaseExtensionAction.ID, TogglePreReleaseExtensionAction.LABEL, TogglePreReleaseExtensionAction.DisabledClass); + this._register(allowedExtensionsService.onDidChangeAllowedExtensionsConfigValue(() => this.update())); this.update(); } @@ -1318,11 +1522,21 @@ export class TogglePreReleaseExtensionAction extends ExtensionAction { if (!this.extension.gallery) { return; } - if (this.extension.preRelease && !this.extension.isPreReleaseVersion) { - return; + if (this.extension.preRelease) { + if (!this.extension.isPreReleaseVersion) { + return; + } + if (this.allowedExtensionsService.isAllowed({ id: this.extension.identifier.id, publisherDisplayName: this.extension.publisherDisplayName }) !== true) { + return; + } } - if (!this.extension.preRelease && !this.extension.gallery.hasPreReleaseVersion) { - return; + if (!this.extension.preRelease) { + if (!this.extension.gallery.hasPreReleaseVersion) { + return; + } + if (this.allowedExtensionsService.isAllowed(this.extension.gallery) !== true) { + return; + } } this.enabled = true; this.class = TogglePreReleaseExtensionAction.EnabledClass; @@ -1348,29 +1562,42 @@ export class TogglePreReleaseExtensionAction extends ExtensionAction { export class InstallAnotherVersionAction extends ExtensionAction { static readonly ID = 'workbench.extensions.action.install.anotherVersion'; - static readonly LABEL = localize('install another version', "Install Another Version..."); + static readonly LABEL = localize('install another version', "Install Specific Version..."); constructor( + extension: IExtension | null, + private readonly whenInstalled: boolean, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IWorkbenchExtensionManagementService private readonly extensionManagementService: IWorkbenchExtensionManagementService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IQuickInputService private readonly quickInputService: IQuickInputService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IDialogService private readonly dialogService: IDialogService, + @IAllowedExtensionsService private readonly allowedExtensionsService: IAllowedExtensionsService, ) { super(InstallAnotherVersionAction.ID, InstallAnotherVersionAction.LABEL, ExtensionAction.LABEL_ACTION_CLASS); + this._register(allowedExtensionsService.onDidChangeAllowedExtensionsConfigValue(() => this.update())); + this.extension = extension; this.update(); } update(): void { - this.enabled = !!this.extension && !this.extension.isBuiltin && !!this.extension.gallery && !!this.extension.local && !!this.extension.server && this.extension.state === ExtensionState.Installed && !this.extension.deprecationInfo; + this.enabled = !!this.extension && !this.extension.isBuiltin && !!this.extension.identifier.uuid && !this.extension.deprecationInfo + && this.allowedExtensionsService.isAllowed({ id: this.extension.identifier.id, publisherDisplayName: this.extension.publisherDisplayName }) === true; + if (this.enabled && this.whenInstalled) { + this.enabled = !!this.extension?.local && !!this.extension.server && this.extension.state === ExtensionState.Installed; + } } override async run(): Promise { if (!this.enabled) { return; } - const targetPlatform = await this.extension!.server!.extensionManagementService.getTargetPlatform(); - const allVersions = await this.extensionGalleryService.getAllCompatibleVersions(this.extension!.gallery!, this.extension!.local!.preRelease, targetPlatform); + if (!this.extension) { + return; + } + const targetPlatform = this.extension.server ? await this.extension.server.extensionManagementService.getTargetPlatform() : await this.extensionManagementService.getTargetPlatform(); + const allVersions = await this.extensionGalleryService.getAllCompatibleVersions(this.extension.identifier, this.extension.local?.preRelease ?? this.extension.gallery?.properties.isPreReleaseVersion ?? false, targetPlatform); if (!allVersions.length) { await this.dialogService.info(localize('no versions', "This extension has no other versions.")); return; @@ -1380,8 +1607,7 @@ export class InstallAnotherVersionAction extends ExtensionAction { return { id: v.version, label: v.version, - description: `${fromNow(new Date(Date.parse(v.date)), true)}${v.isPreReleaseVersion ? ` (${localize('pre-release', "pre-release")})` : ''}${v.version === this.extension!.version ? ` (${localize('current', "current")})` : ''}`, - latest: i === 0, + description: `${fromNow(new Date(Date.parse(v.date)), true)}${v.isPreReleaseVersion ? ` (${localize('pre-release', "pre-release")})` : ''}${v.version === this.extension?.local?.manifest.version ? ` (${localize('current', "current")})` : ''}`, ariaLabel: `${v.isPreReleaseVersion ? 'Pre-Release version' : 'Release version'} ${v.version}`, isPreReleaseVersion: v.isPreReleaseVersion }; @@ -1392,18 +1618,14 @@ export class InstallAnotherVersionAction extends ExtensionAction { matchOnDetail: true }); if (pick) { - if (this.extension!.version === pick.id) { + if (this.extension.local?.manifest.version === pick.id) { return; } + const options = { installPreReleaseVersion: pick.isPreReleaseVersion, version: pick.id }; try { - if (pick.latest) { - const [extension] = pick.id !== this.extension?.version ? await this.extensionsWorkbenchService.getExtensions([{ id: this.extension!.identifier.id, preRelease: pick.isPreReleaseVersion }], CancellationToken.None) : [this.extension]; - await this.extensionsWorkbenchService.install(extension ?? this.extension!, { installPreReleaseVersion: pick.isPreReleaseVersion }); - } else { - await this.extensionsWorkbenchService.install(this.extension!, { installPreReleaseVersion: pick.isPreReleaseVersion, version: pick.id }); - } + await this.extensionsWorkbenchService.install(this.extension, options); } catch (error) { - this.instantiationService.createInstance(PromptExtensionInstallFailureAction, this.extension!, pick.latest ? this.extension!.latestVersion : pick.id, InstallOperation.Install, error).run(); + this.instantiationService.createInstance(PromptExtensionInstallFailureAction, this.extension, options, pick.id, InstallOperation.Install, error).run(); } } return null; @@ -1540,12 +1762,12 @@ export class DisableGloballyAction extends ExtensionAction { } } -export class EnableDropDownAction extends ActionWithDropDownAction { +export class EnableDropDownAction extends ButtonWithDropDownExtensionAction { constructor( @IInstantiationService instantiationService: IInstantiationService ) { - super('extensions.enable', localize('enableAction', "Enable"), [ + super('extensions.enable', ExtensionAction.LABEL_ACTION_CLASS, [ [ instantiationService.createInstance(EnableGloballyAction), instantiationService.createInstance(EnableForWorkspaceAction) @@ -1554,12 +1776,12 @@ export class EnableDropDownAction extends ActionWithDropDownAction { } } -export class DisableDropDownAction extends ActionWithDropDownAction { +export class DisableDropDownAction extends ButtonWithDropDownExtensionAction { constructor( @IInstantiationService instantiationService: IInstantiationService ) { - super('extensions.disable', localize('disableAction', "Disable"), [[ + super('extensions.disable', ExtensionAction.LABEL_ACTION_CLASS, [[ instantiationService.createInstance(DisableGloballyAction), instantiationService.createInstance(DisableForWorkspaceAction) ]]); @@ -1570,7 +1792,7 @@ export class DisableDropDownAction extends ActionWithDropDownAction { export class ExtensionRuntimeStateAction extends ExtensionAction { private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} reload`; - private static readonly DisabledClass = `${ExtensionRuntimeStateAction.EnabledClass} disabled`; + private static readonly DisabledClass = `${this.EnabledClass} disabled`; updateWhenCounterExtensionChanges: boolean = true; @@ -1684,7 +1906,7 @@ export class SetColorThemeAction extends ExtensionAction { static readonly TITLE = localize2('workbench.extensions.action.setColorTheme', 'Set Color Theme'); private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`; - private static readonly DisabledClass = `${SetColorThemeAction.EnabledClass} disabled`; + private static readonly DisabledClass = `${this.EnabledClass} disabled`; constructor( @IExtensionService extensionService: IExtensionService, @@ -1735,7 +1957,7 @@ export class SetFileIconThemeAction extends ExtensionAction { static readonly TITLE = localize2('workbench.extensions.action.setFileIconTheme', 'Set File Icon Theme'); private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`; - private static readonly DisabledClass = `${SetFileIconThemeAction.EnabledClass} disabled`; + private static readonly DisabledClass = `${this.EnabledClass} disabled`; constructor( @IExtensionService extensionService: IExtensionService, @@ -1785,7 +2007,7 @@ export class SetProductIconThemeAction extends ExtensionAction { static readonly TITLE = localize2('workbench.extensions.action.setProductIconTheme', 'Set Product Icon Theme'); private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`; - private static readonly DisabledClass = `${SetProductIconThemeAction.EnabledClass} disabled`; + private static readonly DisabledClass = `${this.EnabledClass} disabled`; constructor( @IExtensionService extensionService: IExtensionService, @@ -1836,7 +2058,7 @@ export class SetLanguageAction extends ExtensionAction { static readonly TITLE = localize2('workbench.extensions.action.setDisplayLanguage', 'Set Display Language'); private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} language`; - private static readonly DisabledClass = `${SetLanguageAction.EnabledClass} disabled`; + private static readonly DisabledClass = `${this.EnabledClass} disabled`; constructor( @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @@ -1848,17 +2070,6 @@ export class SetLanguageAction extends ExtensionAction { update(): void { this.enabled = false; this.class = SetLanguageAction.DisabledClass; - if (!this.extension) { - return; - } - if (!this.extensionsWorkbenchService.canSetLanguage(this.extension)) { - return; - } - if (this.extension.gallery && language === getLocale(this.extension.gallery)) { - return; - } - this.enabled = true; - this.class = SetLanguageAction.EnabledClass; } override async run(): Promise { @@ -1872,10 +2083,9 @@ export class ClearLanguageAction extends ExtensionAction { static readonly TITLE = localize2('workbench.extensions.action.clearLanguage', 'Clear Display Language'); private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} language`; - private static readonly DisabledClass = `${ClearLanguageAction.EnabledClass} disabled`; + private static readonly DisabledClass = `${this.EnabledClass} disabled`; constructor( - @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @ILocaleService private readonly localeService: ILocaleService, ) { super(ClearLanguageAction.ID, ClearLanguageAction.TITLE.value, ClearLanguageAction.DisabledClass, false); @@ -1885,17 +2095,6 @@ export class ClearLanguageAction extends ExtensionAction { update(): void { this.enabled = false; this.class = ClearLanguageAction.DisabledClass; - if (!this.extension) { - return; - } - if (!this.extensionsWorkbenchService.canSetLanguage(this.extension)) { - return; - } - if (this.extension.gallery && language !== getLocale(this.extension.gallery)) { - return; - } - this.enabled = true; - this.class = ClearLanguageAction.EnabledClass; } override async run(): Promise { @@ -1912,7 +2111,6 @@ export class ShowRecommendedExtensionAction extends Action { constructor( extensionId: string, - @IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService, @IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService, ) { super(ShowRecommendedExtensionAction.ID, ShowRecommendedExtensionAction.LABEL, undefined, false); @@ -1920,10 +2118,7 @@ export class ShowRecommendedExtensionAction extends Action { } override async run(): Promise { - const paneComposite = await this.paneCompositeService.openPaneComposite(VIEWLET_ID, ViewContainerLocation.Sidebar, true); - const paneContainer = paneComposite?.getViewPaneContainer() as IExtensionsViewPaneContainer; - paneContainer.search(`@id:${this.extensionId}`); - paneContainer.focus(); + await this.extensionWorkbenchService.openSearch(`@id:${this.extensionId}`); const [extension] = await this.extensionWorkbenchService.getExtensions([{ id: this.extensionId }], { source: 'install-recommendation' }, CancellationToken.None); if (extension) { return this.extensionWorkbenchService.open(extension); @@ -1941,7 +2136,6 @@ export class InstallRecommendedExtensionAction extends Action { constructor( extensionId: string, - @IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService, ) { @@ -1950,17 +2144,14 @@ export class InstallRecommendedExtensionAction extends Action { } override async run(): Promise { - const viewlet = await this.paneCompositeService.openPaneComposite(VIEWLET_ID, ViewContainerLocation.Sidebar, true); - const viewPaneContainer = viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer; - viewPaneContainer.search(`@id:${this.extensionId}`); - viewPaneContainer.focus(); + await this.extensionWorkbenchService.openSearch(`@id:${this.extensionId}`); const [extension] = await this.extensionWorkbenchService.getExtensions([{ id: this.extensionId }], { source: 'install-recommendation' }, CancellationToken.None); if (extension) { await this.extensionWorkbenchService.open(extension); try { await this.extensionWorkbenchService.install(extension); } catch (err) { - this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, extension.latestVersion, InstallOperation.Install, err).run(); + this.instantiationService.createInstance(PromptExtensionInstallFailureAction, extension, undefined, extension.latestVersion, InstallOperation.Install, err).run(); } } } @@ -2012,22 +2203,6 @@ export class UndoIgnoreExtensionRecommendationAction extends Action { } } -export class SearchExtensionsAction extends Action { - - constructor( - private readonly searchValue: string, - @IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService - ) { - super('extensions.searchExtensions', localize('search recommendations', "Search Extensions"), undefined, true); - } - - override async run(): Promise { - const viewPaneContainer = (await this.paneCompositeService.openPaneComposite(VIEWLET_ID, ViewContainerLocation.Sidebar, true))?.getViewPaneContainer() as IExtensionsViewPaneContainer; - viewPaneContainer.search(this.searchValue); - viewPaneContainer.focus(); - } -} - export abstract class AbstractConfigureRecommendedExtensionsAction extends Action { constructor( @@ -2184,7 +2359,7 @@ export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends Abstrac export class ExtensionStatusLabelAction extends Action implements IExtensionContainer { private static readonly ENABLED_CLASS = `${ExtensionAction.TEXT_ACTION_CLASS} extension-status-label`; - private static readonly DISABLED_CLASS = `${ExtensionStatusLabelAction.ENABLED_CLASS} hide`; + private static readonly DISABLED_CLASS = `${this.ENABLED_CLASS} hide`; private initialStatus: ExtensionState | null = null; private status: ExtensionState | null = null; @@ -2255,7 +2430,13 @@ export class ExtensionStatusLabelAction extends Action implements IExtensionCont if (currentStatus !== null) { if (currentStatus === ExtensionState.Installing && this.status === ExtensionState.Installed) { - return canAddExtension() ? this.initialStatus === ExtensionState.Installed && this.version !== currentVersion ? localize('updated', "Updated") : localize('installed', "Installed") : null; + if (this.initialStatus === ExtensionState.Uninstalled && canAddExtension()) { + return localize('installed', "Installed"); + } + if (this.initialStatus === ExtensionState.Installed && this.version !== currentVersion && canAddExtension()) { + return localize('updated', "Updated"); + } + return null; } if (currentStatus === ExtensionState.Uninstalling && this.status === ExtensionState.Uninstalled) { this.initialStatus = this.status; @@ -2284,10 +2465,10 @@ export class ExtensionStatusLabelAction extends Action implements IExtensionCont } -export class ToggleSyncExtensionAction extends ExtensionDropDownAction { +export class ToggleSyncExtensionAction extends DropDownExtensionAction { private static readonly IGNORED_SYNC_CLASS = `${ExtensionAction.ICON_ACTION_CLASS} extension-sync ${ThemeIcon.asClassName(syncIgnoredIcon)}`; - private static readonly SYNC_CLASS = `${ToggleSyncExtensionAction.ICON_ACTION_CLASS} extension-sync ${ThemeIcon.asClassName(syncEnabledIcon)}`; + private static readonly SYNC_CLASS = `${this.ICON_ACTION_CLASS} extension-sync ${ThemeIcon.asClassName(syncEnabledIcon)}`; constructor( @IConfigurationService private readonly configurationService: IConfigurationService, @@ -2311,16 +2492,14 @@ export class ToggleSyncExtensionAction extends ExtensionDropDownAction { } override async run(): Promise { - return super.run({ - actionGroups: [ - [ - new Action( - 'extensions.syncignore', - this.extensionsWorkbenchService.isExtensionIgnoredToSync(this.extension!) ? localize('sync', "Sync this extension") : localize('do not sync', "Do not sync this extension") - , undefined, true, () => this.extensionsWorkbenchService.toggleExtensionIgnoredToSync(this.extension!)) - ] - ], disposeActionsOnHide: true - }); + return super.run([ + [ + new Action( + 'extensions.syncignore', + this.extensionsWorkbenchService.isExtensionIgnoredToSync(this.extension!) ? localize('sync', "Sync this extension") : localize('do not sync', "Do not sync this extension") + , undefined, true, () => this.extensionsWorkbenchService.toggleExtensionIgnoredToSync(this.extension!)) + ] + ]); } } @@ -2332,8 +2511,8 @@ export class ExtensionStatusAction extends ExtensionAction { updateWhenCounterExtensionChanges: boolean = true; - private _status: ExtensionStatus | undefined; - get status(): ExtensionStatus | undefined { return this._status; } + private _status: ExtensionStatus[] = []; + get status(): ExtensionStatus[] { return this._status; } private readonly _onDidChangeStatus = this._register(new Emitter()); readonly onDidChangeStatus = this._onDidChangeStatus.event; @@ -2351,13 +2530,16 @@ export class ExtensionStatusAction extends ExtensionAction { @IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IProductService private readonly productService: IProductService, + @IAllowedExtensionsService private readonly allowedExtensionsService: IAllowedExtensionsService, @IWorkbenchExtensionEnablementService private readonly workbenchExtensionEnablementService: IWorkbenchExtensionEnablementService, @IExtensionFeaturesManagementService private readonly extensionFeaturesManagementService: IExtensionFeaturesManagementService, + @IExtensionGalleryManifestService private readonly extensionGalleryManifestService: IExtensionGalleryManifestService, ) { super('extensions.status', '', `${ExtensionStatusAction.CLASS} hide`, false); this._register(this.labelService.onDidChangeFormatters(() => this.update(), this)); this._register(this.extensionService.onDidChangeExtensions(() => this.update())); this._register(this.extensionFeaturesManagementService.onDidChangeAccessData(() => this.update())); + this._register(allowedExtensionsService.onDidChangeAllowedExtensionsConfigValue(() => this.update())); this.update(); } @@ -2378,6 +2560,11 @@ export class ExtensionStatusAction extends ExtensionAction { return; } + if (this.extension.state === ExtensionState.Uninstalled && this.extension.gallery && !this.extension.gallery.isSigned && shouldRequireRepositorySignatureFor(this.extension.private, await this.extensionGalleryManifestService.getExtensionGalleryManifest())) { + this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('not signed tooltip', "This extension is not signed by the Extension Marketplace.")) }, true); + return; + } + if (this.extension.deprecationInfo) { if (this.extension.deprecationInfo.extension) { const link = `[${this.extension.deprecationInfo.extension.displayName}](${URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.deprecationInfo.extension.id]))}`)})`; @@ -2395,22 +2582,36 @@ export class ExtensionStatusAction extends ExtensionAction { return; } + if (this.extension.missingFromGallery) { + this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('missing from gallery tooltip', "This extension is no longer available on the Extension Marketplace.")) }, true); + return; + } + if (this.extensionsWorkbenchService.canSetLanguage(this.extension)) { return; } - if (this.extension.gallery && this.extension.state === ExtensionState.Uninstalled && !await this.extensionsWorkbenchService.canInstall(this.extension)) { - if (this.extensionManagementServerService.localExtensionManagementServer || this.extensionManagementServerService.remoteExtensionManagementServer) { - const targetPlatform = await (this.extensionManagementServerService.localExtensionManagementServer ? this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.getTargetPlatform() : this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.getTargetPlatform()); - const message = new MarkdownString(`${localize('incompatible platform', "The '{0}' extension is not available in {1} for {2}.", this.extension.displayName || this.extension.identifier.id, this.productService.nameLong, TargetPlatformToString(targetPlatform))} [${localize('learn more', "Learn More")}](https://aka.ms/vscode-platform-specific-extensions)`); - this.updateStatus({ icon: warningIcon, message }, true); - return; + if (this.extension.outdated) { + const message = await this.extensionsWorkbenchService.shouldRequireConsentToUpdate(this.extension); + if (message) { + const markdown = new MarkdownString(); + markdown.appendMarkdown(`${message} `); + markdown.appendMarkdown( + localize('auto update message', "Please [review the extension]({0}) and update it manually.", + this.extension.hasChangelog() + ? URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.identifier.id, ExtensionEditorTab.Changelog]))}`).toString() + : this.extension.repository + ? this.extension.repository + : URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.identifier.id]))}`).toString() + )); + this.updateStatus({ icon: warningIcon, message: markdown }, true); } + } - if (this.extensionManagementServerService.webExtensionManagementServer) { - const productName = localize('VS Code for Web', "{0} for the Web", this.productService.nameLong); - const message = new MarkdownString(`${localize('not web tooltip', "The '{0}' extension is not available in {1}.", this.extension.displayName || this.extension.identifier.id, productName)} [${localize('learn why', "Learn Why")}](https://aka.ms/vscode-web-extensions-guide)`); - this.updateStatus({ icon: warningIcon, message }, true); + if (this.extension.gallery && this.extension.state === ExtensionState.Uninstalled) { + const result = await this.extensionsWorkbenchService.canInstall(this.extension); + if (result !== true) { + this.updateStatus({ icon: warningIcon, message: result }, true); return; } } @@ -2422,6 +2623,15 @@ export class ExtensionStatusAction extends ExtensionAction { return; } + // Extension is disabled by allowed list + if (this.extension.enablementState === EnablementState.DisabledByAllowlist) { + const result = this.allowedExtensionsService.isAllowed(this.extension.local); + if (result !== true) { + this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('disabled - not allowed', "This extension is disabled because {0}", result.value)) }, true); + return; + } + } + // Extension is disabled by environment if (this.extension.enablementState === EnablementState.DisabledByEnvironment) { this.updateStatus({ message: new MarkdownString(localize('disabled by environment', "This extension is disabled by the environment.")) }, true); @@ -2451,10 +2661,11 @@ export class ExtensionStatusAction extends ExtensionAction { } } - // Extension is disabled by untrusted workspace - if (this.extension.enablementState === EnablementState.DisabledByTrustRequirement || - // All disabled dependencies of the extension are disabled by untrusted workspace - (this.extension.enablementState === EnablementState.DisabledByExtensionDependency && this.workbenchExtensionEnablementService.getDependenciesEnablementStates(this.extension.local).every(([, enablementState]) => this.workbenchExtensionEnablementService.isEnabledEnablementState(enablementState) || enablementState === EnablementState.DisabledByTrustRequirement))) { + if (!this.workspaceTrustService.isWorkspaceTrusted() && + // Extension is disabled by untrusted workspace + (this.extension.enablementState === EnablementState.DisabledByTrustRequirement || + // All disabled dependencies of the extension are disabled by untrusted workspace + (this.extension.enablementState === EnablementState.DisabledByExtensionDependency && this.workbenchExtensionEnablementService.getDependenciesEnablementStates(this.extension.local).every(([, enablementState]) => this.workbenchExtensionEnablementService.isEnabledEnablementState(enablementState) || enablementState === EnablementState.DisabledByTrustRequirement)))) { this.enabled = true; const untrustedDetails = getWorkspaceSupportTypeMessage(this.extension.local.manifest.capabilities?.untrustedWorkspaces); this.updateStatus({ icon: trustIcon, message: new MarkdownString(untrustedDetails ? escapeMarkdownSyntaxTokens(untrustedDetails) : localize('extension disabled because of trust requirement', "This extension has been disabled because the current workspace is not trusted.")) }, true); @@ -2558,7 +2769,17 @@ export class ExtensionStatusAction extends ExtensionAction { // Extension is disabled by its dependency if (this.extension.enablementState === EnablementState.DisabledByExtensionDependency) { - this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('extension disabled because of dependency', "This extension has been disabled because it depends on an extension that is disabled.")) }, true); + this.updateStatus({ + icon: warningIcon, + message: new MarkdownString(localize('extension disabled because of dependency', "This extension depends on an extension that is disabled.")) + .appendMarkdown(` [${localize('dependencies', "Show Dependencies")}](${URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.identifier.id, ExtensionEditorTab.Dependencies]))}`)})`) + }, true); + return; + } + + if (!this.extension.local.isValid) { + const errors = this.extension.local.validations.filter(([severity]) => severity === Severity.Error).map(([, message]) => message); + this.updateStatus({ icon: warningIcon, message: new MarkdownString(errors.join(' ').trim()) }, true); return; } @@ -2591,33 +2812,46 @@ export class ExtensionStatusAction extends ExtensionAction { return; } } - - if (isEnabled && !isRunning && !this.extension.local.isValid) { - const errors = this.extension.local.validations.filter(([severity]) => severity === Severity.Error).map(([, message]) => message); - this.updateStatus({ icon: errorIcon, message: new MarkdownString(errors.join(' ').trim()) }, true); - } - } private updateStatus(status: ExtensionStatus | undefined, updateClass: boolean): void { - if (this._status === status) { - return; + if (status) { + if (this._status.some(s => s.message.value === status.message.value && s.icon?.id === status.icon?.id)) { + return; + } + } else { + if (this._status.length === 0) { + return; + } + this._status = []; } - if (this._status && status && this._status.message === status.message && this._status.icon?.id === status.icon?.id) { - return; + + if (status) { + this._status.push(status); + this._status.sort((a, b) => + b.icon === trustIcon ? -1 : + a.icon === trustIcon ? 1 : + b.icon === errorIcon ? -1 : + a.icon === errorIcon ? 1 : + b.icon === warningIcon ? -1 : + a.icon === warningIcon ? 1 : + b.icon === infoIcon ? -1 : + a.icon === infoIcon ? 1 : + 0 + ); } - this._status = status; + if (updateClass) { - if (this._status?.icon === errorIcon) { + if (status?.icon === errorIcon) { this.class = `${ExtensionStatusAction.CLASS} extension-status-error ${ThemeIcon.asClassName(errorIcon)}`; } - else if (this._status?.icon === warningIcon) { + else if (status?.icon === warningIcon) { this.class = `${ExtensionStatusAction.CLASS} extension-status-warning ${ThemeIcon.asClassName(warningIcon)}`; } - else if (this._status?.icon === infoIcon) { + else if (status?.icon === infoIcon) { this.class = `${ExtensionStatusAction.CLASS} extension-status-info ${ThemeIcon.asClassName(infoIcon)}`; } - else if (this._status?.icon === trustIcon) { + else if (status?.icon === trustIcon) { this.class = `${ExtensionStatusAction.CLASS} ${ThemeIcon.asClassName(trustIcon)}`; } else { @@ -2628,79 +2862,12 @@ export class ExtensionStatusAction extends ExtensionAction { } override async run(): Promise { - if (this._status?.icon === trustIcon) { + if (this._status[0]?.icon === trustIcon) { return this.commandService.executeCommand('workbench.trust.manage'); } } } -export class ReinstallAction extends Action { - - static readonly ID = 'workbench.extensions.action.reinstall'; - static readonly LABEL = localize('reinstall', "Reinstall Extension..."); - - constructor( - id: string = ReinstallAction.ID, label: string = ReinstallAction.LABEL, - @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, - @IQuickInputService private readonly quickInputService: IQuickInputService, - @INotificationService private readonly notificationService: INotificationService, - @IHostService private readonly hostService: IHostService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IExtensionService private readonly extensionService: IExtensionService - ) { - super(id, label); - } - - override get enabled(): boolean { - return this.extensionsWorkbenchService.local.filter(l => !l.isBuiltin && l.local).length > 0; - } - - override run(): Promise { - return this.quickInputService.pick(this.getEntries(), { placeHolder: localize('selectExtensionToReinstall', "Select Extension to Reinstall") }) - .then(pick => pick && this.reinstallExtension(pick.extension)); - } - - private getEntries(): Promise<(IQuickPickItem & { extension: IExtension })[]> { - return this.extensionsWorkbenchService.queryLocal() - .then(local => { - const entries = local - .filter(extension => !extension.isBuiltin && extension.server !== this.extensionManagementServerService.webExtensionManagementServer) - .map(extension => { - return { - id: extension.identifier.id, - label: extension.displayName, - description: extension.identifier.id, - extension, - }; - }); - return entries; - }); - } - - private reinstallExtension(extension: IExtension): Promise { - return this.instantiationService.createInstance(SearchExtensionsAction, '@installed ').run() - .then(() => { - return this.extensionsWorkbenchService.reinstall(extension) - .then(extension => { - const requireReload = !(extension.local && this.extensionService.canAddExtension(toExtensionDescription(extension.local))); - const message = requireReload ? localize('ReinstallAction.successReload', "Please reload Visual Studio Code to complete reinstalling the extension {0}.", extension.identifier.id) - : localize('ReinstallAction.success', "Reinstalling the extension {0} is completed.", extension.identifier.id); - const actions = requireReload ? [{ - label: localize('InstallVSIXAction.reloadNow', "Reload Now"), - run: () => this.hostService.reload() - }] : []; - this.notificationService.prompt( - Severity.Info, - message, - actions, - { sticky: true } - ); - }, error => this.notificationService.error(error)); - }); - } -} - export class InstallSpecificVersionOfExtensionAction extends Action { static readonly ID = 'workbench.extensions.action.install.specificVersion'; @@ -2723,16 +2890,14 @@ export class InstallSpecificVersionOfExtensionAction extends Action { override async run(): Promise { const extensionPick = await this.quickInputService.pick(this.getExtensionEntries(), { placeHolder: localize('selectExtension', "Select Extension"), matchOnDetail: true }); if (extensionPick && extensionPick.extension) { - const action = this.instantiationService.createInstance(InstallAnotherVersionAction); - action.extension = extensionPick.extension; + const action = this.instantiationService.createInstance(InstallAnotherVersionAction, extensionPick.extension, true); await action.run(); - await this.instantiationService.createInstance(SearchExtensionsAction, extensionPick.extension.identifier.id).run(); + await this.extensionsWorkbenchService.openSearch(extensionPick.extension.identifier.id); } } private isEnabled(extension: IExtension): boolean { - const action = this.instantiationService.createInstance(InstallAnotherVersionAction); - action.extension = extension; + const action = this.instantiationService.createInstance(InstallAnotherVersionAction, extension, true); return action.enabled && !!extension.local && this.extensionEnablementService.isEnabled(extension.local); } @@ -2969,29 +3134,14 @@ export class InstallRemoteExtensionsInLocalAction extends AbstractInstallExtensi } CommandsRegistry.registerCommand('workbench.extensions.action.showExtensionsForLanguage', function (accessor: ServicesAccessor, fileExtension: string) { - const paneCompositeService = accessor.get(IPaneCompositePartService); - - return paneCompositeService.openPaneComposite(VIEWLET_ID, ViewContainerLocation.Sidebar, true) - .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) - .then(viewlet => { - viewlet.search(`ext:${fileExtension.replace(/^\./, '')}`); - viewlet.focus(); - }); + const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService); + return extensionsWorkbenchService.openSearch(`ext:${fileExtension.replace(/^\./, '')}`); }); export const showExtensionsWithIdsCommandId = 'workbench.extensions.action.showExtensionsWithIds'; CommandsRegistry.registerCommand(showExtensionsWithIdsCommandId, function (accessor: ServicesAccessor, extensionIds: string[]) { - const paneCompositeService = accessor.get(IPaneCompositePartService); - - return paneCompositeService.openPaneComposite(VIEWLET_ID, ViewContainerLocation.Sidebar, true) - .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) - .then(viewlet => { - const query = extensionIds - .map(id => `@id:${id}`) - .join(' '); - viewlet.search(query); - viewlet.focus(); - }); + const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService); + return extensionsWorkbenchService.openSearch(extensionIds.map(id => `@id:${id}`).join(' ')); }); registerColor('extensionButton.background', { @@ -3015,12 +3165,7 @@ registerColor('extensionButton.hoverBackground', { hcLight: null }, localize('extensionButtonHoverBackground', "Button background hover color for extension actions.")); -registerColor('extensionButton.separator', { - dark: buttonSeparator, - light: buttonSeparator, - hcDark: buttonSeparator, - hcLight: buttonSeparator -}, localize('extensionButtonSeparator', "Button separator color for extension actions")); +registerColor('extensionButton.separator', buttonSeparator, localize('extensionButtonSeparator', "Button separator color for extension actions")); export const extensionButtonProminentBackground = registerColor('extensionButton.prominentBackground', { dark: buttonBackground, diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 7ca65d956..305dbe302 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -64,6 +64,8 @@ import { ILocalizedString } from 'vs/platform/action/common/action'; import { registerNavigableContainer } from 'vs/workbench/browser/actions/widgetNavigationCommands'; import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { memoize } from 'vs/base/common/decorators'; export const DefaultViewsContext = new RawContextKey('defaultExtensionViews', true); export const ExtensionsSortByContext = new RawContextKey('extensionsSortByValue', ''); @@ -87,7 +89,6 @@ const SortByUpdateDateContext = new RawContextKey('sortByUpdateDate', f const REMOTE_CATEGORY: ILocalizedString = localize2({ key: 'remote', comment: ['Remote as in remote machine'] }, "Remote"); export class ExtensionsViewletViewsContribution extends Disposable implements IWorkbenchContribution { - private readonly container: ViewContainer; constructor( @@ -516,7 +517,8 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE @IExtensionService extensionService: IExtensionService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IPreferencesService private readonly preferencesService: IPreferencesService, - @ICommandService private readonly commandService: ICommandService + @ICommandService private readonly commandService: ICommandService, + @IProductService private readonly productService: IProductService, ) { super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); @@ -544,6 +546,15 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE this.searchViewletState = this.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE); } + @memoize + get extensionsGalleryHostname(): string { + if (this.productService.extensionsGallery?.serviceUrl) { + return new URL(this.productService.extensionsGallery?.serviceUrl).hostname; + } + + return 'Marketplace'; + } + get searchValue(): string | undefined { return this.searchBox?.getValue(); } @@ -558,8 +569,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE hide(overlay); const header = append(this.root, $('.header')); - const placeholder = localize('searchExtensions', "Search Extensions in Marketplace"); - + const placeholder = localize('searchExtensions', 'Search extensions in {0}', this.extensionsGalleryHostname); const searchValue = this.searchViewletState['query.value'] ? this.searchViewletState['query.value'] : ''; const searchContainer = append(header, $('.extensions-search-container')); @@ -924,4 +934,4 @@ export class MaliciousExtensionChecker implements IWorkbenchContribution { }).then(() => undefined); }, err => this.logService.error(err)); } -} +} \ No newline at end of file diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 3eb8c7ee1..83cb13bb0 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -950,6 +950,17 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension urlService.registerHandler(this); this.whenInitialized = this.initialize(); + + //Update workspace to open notebook + const urlParams = new URLSearchParams(window.location.search); + const notebookKey = urlParams.get('openNotebook'); + const clusterId = urlParams.get('clusterId'); + const region = urlParams.get('region'); + configurationService.updateValue('extensions.openNotebookData', { + notebookKey: notebookKey, + clusterId: clusterId, + region: region, + }); } private async initialize(): Promise { diff --git a/patched-vscode/src/vs/workbench/services/localization/electron-sandbox/localeService.ts b/patched-vscode/src/vs/workbench/services/localization/electron-sandbox/localeService.ts index d7124758e..92013033d 100644 --- a/patched-vscode/src/vs/workbench/services/localization/electron-sandbox/localeService.ts +++ b/patched-vscode/src/vs/workbench/services/localization/electron-sandbox/localeService.ts @@ -3,25 +3,25 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Language, LANGUAGE_DEFAULT } from 'vs/base/common/platform'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; -import { IActiveLanguagePackService, ILocaleService } from 'vs/workbench/services/localization/common/locale'; -import { ILanguagePackItem, ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; -import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; -import { IViewPaneContainer, ViewContainerLocation } from 'vs/workbench/common/views'; -import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { localize } from 'vs/nls'; -import { toAction } from 'vs/base/common/actions'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { stripComments } from 'vs/base/common/stripComments'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { Language, LANGUAGE_DEFAULT } from '../../../../base/common/platform.js'; +import { IEnvironmentService } from '../../../../platform/environment/common/environment.js'; +import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js'; +import { IJSONEditingService } from '../../configuration/common/jsonEditing.js'; +import { IActiveLanguagePackService, ILocaleService } from '../common/locale.js'; +import { ILanguagePackItem, ILanguagePackService } from '../../../../platform/languagePacks/common/languagePacks.js'; +import { IPaneCompositePartService } from '../../panecomposite/browser/panecomposite.js'; +import { IViewPaneContainer, ViewContainerLocation } from '../../../common/views.js'; +import { IExtensionManagementService } from '../../../../platform/extensionManagement/common/extensionManagement.js'; +import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js'; +import { localize } from '../../../../nls.js'; +import { toAction } from '../../../../base/common/actions.js'; +import { ITextFileService } from '../../textfile/common/textfiles.js'; +import { parse } from '../../../../base/common/jsonc.js'; +import { IEditorService } from '../../editor/common/editorService.js'; +import { IHostService } from '../../host/browser/host.js'; +import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; +import { IProductService } from '../../../../platform/product/common/productService.js'; +import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; // duplicate of IExtensionsViewPaneContainer in contrib interface IExtensionsViewPaneContainer extends IViewPaneContainer { @@ -51,13 +51,14 @@ class NativeLocaleService implements ILocaleService { @IProductService private readonly productService: IProductService ) { } - private async validateLocaleFile(): Promise { + // Make public just so we do not have to patch all the unused code out. + public async validateLocaleFile(): Promise { try { const content = await this.textFileService.read(this.environmentService.argvResource, { encoding: 'utf8' }); // This is the same logic that we do where argv.json is parsed so mirror that: // https://github.com/microsoft/vscode/blob/32d40cf44e893e87ac33ac4f08de1e5f7fe077fc/src/main.js#L238-L246 - JSON.parse(stripComments(content.value)); + parse(content.value); } catch (error) { this.notificationService.notify({ severity: Severity.Error, @@ -78,9 +79,6 @@ class NativeLocaleService implements ILocaleService { } private async writeLocaleValue(locale: string | undefined): Promise { - if (!(await this.validateLocaleFile())) { - return false; - } await this.jsonEditingService.write(this.environmentService.argvResource, [{ path: ['locale'], value: locale }], true); return true; } diff --git a/patched-vscode/src/vs/workbench/workbench.web.main.ts b/patched-vscode/src/vs/workbench/workbench.web.main.ts index e4a0a7e3e..bdbc65d66 100644 --- a/patched-vscode/src/vs/workbench/workbench.web.main.ts +++ b/patched-vscode/src/vs/workbench/workbench.web.main.ts @@ -52,7 +52,7 @@ import 'vs/workbench/services/dialogs/browser/fileDialogService'; import 'vs/workbench/services/host/browser/browserHostService'; import 'vs/workbench/services/lifecycle/browser/lifecycleService'; import 'vs/workbench/services/clipboard/browser/clipboardService'; -import 'vs/workbench/services/localization/browser/localeService'; +import 'vs/workbench/services/localization/electron-sandbox/localeService'; import 'vs/workbench/services/path/browser/pathService'; import 'vs/workbench/services/themes/browser/browserHostColorSchemeService'; import 'vs/workbench/services/encryption/browser/encryptionService'; @@ -118,8 +118,9 @@ registerSingleton(ILanguagePackService, WebLanguagePacksService, InstantiationTy // Logs import 'vs/workbench/contrib/logs/browser/logs.contribution'; -// Localization -import 'vs/workbench/contrib/localization/browser/localization.contribution'; +// Localization. This does not actually import anything specific to Electron so +// it should be safe. +import 'vs/workbench/contrib/localization/electron-sandbox/localization.contribution'; // Performance import 'vs/workbench/contrib/performance/browser/performance.web.contribution'; diff --git a/patched-vscode/yarn.lock b/patched-vscode/yarn.lock index f2613ceef..4d02c39b1 100644 --- a/patched-vscode/yarn.lock +++ b/patched-vscode/yarn.lock @@ -2618,11 +2618,11 @@ braces@^2.3.1, braces@^2.3.2: to-regex "^3.0.1" braces@^3.0.1, braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" browser-stdout@1.3.1: version "1.3.1" @@ -4569,10 +4569,10 @@ fill-range@^4.0.0: repeat-string "^1.6.1" to-regex-range "^2.1.0" -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +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== dependencies: to-regex-range "^5.0.1" @@ -4732,9 +4732,9 @@ form-data@^3.0.0: mime-types "^2.1.12" form-data@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + version "4.0.4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.4.tgz#784cdcce0669a9d68e94d11ac4eea98088edd2c4" + integrity sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow== dependencies: asynckit "^0.4.0" combined-stream "^1.0.8" @@ -6874,27 +6874,27 @@ micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: to-regex "^3.0.2" micromatch@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" - integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.1" - picomatch "^2.0.5" + braces "^3.0.3" + picomatch "^2.3.1" micromatch@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" - integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.1" - picomatch "^2.2.3" + braces "^3.0.3" + picomatch "^2.3.1" micromatch@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.2" + braces "^3.0.3" picomatch "^2.3.1" mime-db@1.45.0: @@ -10665,9 +10665,9 @@ write@1.0.3: mkdirp "^0.5.1" ws@^7.2.0: - version "7.4.6" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" - integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== xml2js@^0.4.19: version "0.4.23" diff --git a/patches/base-path-compatibility.diff b/patches/base-path-compatibility.diff new file mode 100644 index 000000000..be35c8e6c --- /dev/null +++ b/patches/base-path-compatibility.diff @@ -0,0 +1,52 @@ +Index: sagemaker-code-editor/vscode/src/vs/server/node/serverEnvironmentService.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/server/node/serverEnvironmentService.ts ++++ sagemaker-code-editor/vscode/src/vs/server/node/serverEnvironmentService.ts +@@ -9,6 +9,7 @@ + import { OPTIONS, OptionDescriptions } from '../../platform/environment/node/argv.js'; + import { refineServiceDecorator } from '../../platform/instantiation/common/instantiation.js'; + import { IEnvironmentService, INativeEnvironmentService } from '../../platform/environment/common/environment.js'; ++import { IProductService } from '../../platform/product/common/productService.js'; + import { memoize } from '../../base/common/decorators.js'; + import { URI } from '../../base/common/uri.js'; + +@@ -91,6 +92,9 @@ + + 'compatibility': { type: 'string' }, + ++ /* ----- sagemaker ----- */ ++ 'base-path': { type: 'string' }, ++ + _: OPTIONS['_'] + }; + +@@ -217,6 +221,9 @@ + + compatibility: string; + ++ /* ----- sagemaker ----- */ ++ 'base-path'?: string, ++ + _: string[]; + } + +@@ -227,7 +234,17 @@ + } + + export class ServerEnvironmentService extends NativeEnvironmentService implements IServerEnvironmentService { ++ ++ constructor(args: ServerParsedArgs, productService: IProductService) { ++ /* ----- sagemaker compatibility: map --base-path to --server-base-path ----- */ ++ if (args['base-path']) { ++ args['server-base-path'] = args['base-path']; ++ } ++ ++ super(args, productService); ++ } ++ + @memoize + override get userRoamingDataHome(): URI { return this.appSettingsHome; } + override get args(): ServerParsedArgs { return super.args as ServerParsedArgs; } +-} ++} +\ No newline at end of file diff --git a/patches/base-path.diff b/patches/base-path.diff index e729f9075..d4814a993 100644 --- a/patches/base-path.diff +++ b/patches/base-path.diff @@ -29,28 +29,28 @@ Index: sagemaker-code-editor/vscode/src/vs/code/browser/workbench/workbench-dev. =================================================================== --- sagemaker-code-editor.orig/vscode/src/vs/code/browser/workbench/workbench-dev.html +++ sagemaker-code-editor/vscode/src/vs/code/browser/workbench/workbench-dev.html -@@ -38,7 +38,7 @@ - - +@@ -36,7 +36,7 @@ + + + - + + + + + +Index: sagemaker-code-editor/vscode/src/vs/platform/environment/common/environmentService.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/platform/environment/common/environmentService.ts ++++ sagemaker-code-editor/vscode/src/vs/platform/environment/common/environmentService.ts +@@ -101,7 +101,7 @@ export abstract class AbstractNativeEnvi + return URI.file(join(vscodePortable, 'argv.json')); + } + +- return joinPath(this.userHome, this.productService.dataFolderName, 'argv.json'); ++ return joinPath(this.appSettingsHome, 'argv.json'); + } + + @memoize + +Index: sagemaker-code-editor/vscode/src/vs/platform/languagePacks/browser/languagePacks.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/platform/languagePacks/browser/languagePacks.ts ++++ sagemaker-code-editor/vscode/src/vs/platform/languagePacks/browser/languagePacks.ts +@@ -5,18 +5,24 @@ + + import { CancellationTokenSource } from '../../../base/common/cancellation.js'; + import { URI } from '../../../base/common/uri.js'; ++import { ProxyChannel } from '../../../base/parts/ipc/common/ipc.js'; + import { IExtensionGalleryService } from '../../extensionManagement/common/extensionManagement.js'; + import { IExtensionResourceLoaderService } from '../../extensionResourceLoader/common/extensionResourceLoader.js'; +-import { ILanguagePackItem, LanguagePackBaseService } from '../common/languagePacks.js'; ++import { ILanguagePackItem, ILanguagePackService, LanguagePackBaseService } from '../common/languagePacks.js'; + import { ILogService } from '../../log/common/log.js'; ++import { IRemoteAgentService } from '../../../workbench/services/remote/common/remoteAgentService.js'; + + export class WebLanguagePacksService extends LanguagePackBaseService { ++ private readonly languagePackService: ILanguagePackService; ++ + constructor( ++ @IRemoteAgentService remoteAgentService: IRemoteAgentService, + @IExtensionResourceLoaderService private readonly extensionResourceLoaderService: IExtensionResourceLoaderService, + @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, + @ILogService private readonly logService: ILogService + ) { + super(extensionGalleryService); ++ this.languagePackService = ProxyChannel.toService(remoteAgentService.getConnection()!.getChannel('languagePacks')) + } + + async getBuiltInExtensionTranslationsUri(id: string, language: string): Promise { +@@ -72,6 +78,6 @@ export class WebLanguagePacksService ext + + // Web doesn't have a concept of language packs, so we just return an empty array + getInstalledLanguages(): Promise { +- return Promise.resolve([]); ++ return this.languagePackService.getInstalledLanguages() + } + } + +Index: sagemaker-code-editor/vscode/src/vs/server/node/serverEnvironmentService.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/server/node/serverEnvironmentService.ts ++++ sagemaker-code-editor/vscode/src/vs/server/node/serverEnvironmentService.ts +@@ -14,6 +14,8 @@ import { URI } from 'vs/base/common/uri' + + export const serverOptions: OptionDescriptions> = { + ++ 'locale': { type: 'string' }, ++ + /* ----- server setup ----- */ + + 'host': { type: 'string', cat: 'o', args: 'ip-address', description: nls.localize('host', "The host name or IP address the server should listen to. If not set, defaults to 'localhost'.") }, +@@ -97,6 +99,8 @@ export const serverOptions: OptionDescri + + export interface ServerParsedArgs { + ++ 'locale'?: string; ++ + /* ----- server setup ----- */ + + host?: string; + +Index: sagemaker-code-editor/vscode/src/vs/server/node/serverServices.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/server/node/serverServices.ts ++++ sagemaker-code-editor/vscode/src/vs/server/node/serverServices.ts +@@ -11,7 +11,7 @@ import * as path from '.. + import { IURITransformer } from '../../base/common/uriIpc.js'; + import { getMachineId, getSqmMachineId, getdevDeviceId } from '../../base/node/id.js'; + import { Promises } from '../../base/node/pfs.js'; +-import { ClientConnectionEvent, IMessagePassingProtocol, IPCServer, StaticRouter } from '../../base/parts/ipc/common/ipc.js'; ++import { ClientConnectionEvent, IMessagePassingProtocol, IPCServer, ProxyChannel, StaticRouter } from '../../base/parts/ipc/common/ipc.js'; + import { ProtocolConstants } from '../../base/parts/ipc/common/ipc.net.js'; + import { IConfigurationService } from '../../platform/configuration/common/configuration.js'; + import { ConfigurationService } from '../../platform/configuration/common/configurationService.js'; +@@ -225,6 +225,9 @@ export async function setupServerService + const channel = new ExtensionManagementChannel(extensionManagementService, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority)); + socketServer.registerChannel('extensions', channel); + ++ const languagePackChannel = ProxyChannel.fromService(accessor.get(ILanguagePackService), disposables); ++ socketServer.registerChannel('languagePacks', languagePackChannel); ++ + // clean up extensions folder + remoteExtensionsScanner.whenExtensionsReady().then(() => extensionManagementService.cleanUp()); + + +Index: sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/server/node/webClientServer.ts ++++ sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts +@@ -401,7 +405,7 @@ export class WebClientServer { + `frame-src 'self' https://*.vscode-cdn.net data:;`, + 'worker-src \'self\' data: blob:;', + 'style-src \'self\' \'unsafe-inline\';', +- 'connect-src \'self\' ws: wss: https://main.vscode-cdn.net http://localhost:* https://localhost:* https://login.microsoftonline.com/ https://update.code.visualstudio.com https://*.vscode-unpkg.net/ https://default.exp-tas.com/vscode/ab https://vscode-sync.trafficmanager.net https://vscode-sync-insiders.trafficmanager.net https://*.gallerycdn.vsassets.io https://marketplace.visualstudio.com https://az764295.vo.msecnd.net https://code.visualstudio.com https://*.gallery.vsassets.io https://*.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com https://*.servicebus.windows.net/ https://vscode.blob.core.windows.net https://vscode.search.windows.net https://vsmarketplacebadges.dev https://vscode.download.prss.microsoft.com https://download.visualstudio.microsoft.com https://*.vscode-unpkg.net https://open-vsx.org;', ++ 'connect-src \'self\' ws: wss: https://main.vscode-cdn.net http://localhost:* https://localhost:* https://login.microsoftonline.com/ https://update.code.visualstudio.com https://*.vscode-unpkg.net/ https://default.exp-tas.com/vscode/ab https://vscode-sync.trafficmanager.net https://vscode-sync-insiders.trafficmanager.net https://*.gallerycdn.vsassets.io https://marketplace.visualstudio.com https://openvsxorg.blob.core.windows.net https://az764295.vo.msecnd.net https://code.visualstudio.com https://*.gallery.vsassets.io https://*.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com https://*.servicebus.windows.net/ https://vscode.blob.core.windows.net https://vscode.search.windows.net https://vsmarketplacebadges.dev https://vscode.download.prss.microsoft.com https://download.visualstudio.microsoft.com https://*.vscode-unpkg.net https://open-vsx.org;', + 'font-src \'self\' blob:;', + 'manifest-src \'self\';' + ].join(' '); + +Index: sagemaker-code-editor/vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts ++++ sagemaker-code-editor/vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +@@ -475,9 +475,6 @@ export class InstallAction extends Exten + if (this.extension.isBuiltin) { + return; + } +- if (this.extensionsWorkbenchService.canSetLanguage(this.extension)) { +- return; +- } + if (this.extension.state !== ExtensionState.Uninstalled) { + return; + } +@@ -782,7 +779,7 @@ export abstract class InstallInOtherServ + } + + if (isLanguagePackExtension(this.extension.local.manifest)) { +- return true; ++ return false; + } + + // Prefers to run on UI +@@ -2073,17 +2070,6 @@ export class SetLanguageAction extends E + update(): void { + this.enabled = false; + this.class = SetLanguageAction.DisabledClass; +- if (!this.extension) { +- return; +- } +- if (!this.extensionsWorkbenchService.canSetLanguage(this.extension)) { +- return; +- } +- if (this.extension.gallery && language === getLocale(this.extension.gallery)) { +- return; +- } +- this.enabled = true; +- this.class = SetLanguageAction.EnabledClass; + } + + override async run(): Promise { +@@ -2100,7 +2086,6 @@ export class ClearLanguageAction extends + private static readonly DisabledClass = `${ClearLanguageAction.EnabledClass} disabled`; + + constructor( +- @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @ILocaleService private readonly localeService: ILocaleService, + ) { + super(ClearLanguageAction.ID, ClearLanguageAction.TITLE.value, ClearLanguageAction.DisabledClass, false); +@@ -2110,17 +2095,6 @@ export class ClearLanguageAction extends + update(): void { + this.enabled = false; + this.class = ClearLanguageAction.DisabledClass; +- if (!this.extension) { +- return; +- } +- if (!this.extensionsWorkbenchService.canSetLanguage(this.extension)) { +- return; +- } +- if (this.extension.gallery && language !== getLocale(this.extension.gallery)) { +- return; +- } +- this.enabled = true; +- this.class = ClearLanguageAction.EnabledClass; + } + + override async run(): Promise { + +Index: sagemaker-code-editor/vscode/src/vs/workbench/services/localization/electron-sandbox/localeService.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/workbench/services/localization/electron-sandbox/localeService.ts ++++ sagemaker-code-editor/vscode/src/vs/workbench/services/localization/electron-sandbox/localeService.ts +@@ -51,7 +51,8 @@ class NativeLocaleService implements ILo + @IProductService private readonly productService: IProductService + ) { } + +- private async validateLocaleFile(): Promise { ++ // Make public just so we do not have to patch all the unused code out. ++ public async validateLocaleFile(): Promise { + try { + const content = await this.textFileService.read(this.environmentService.argvResource, { encoding: 'utf8' }); + +@@ -78,9 +79,6 @@ class NativeLocaleService implements ILo + } + + private async writeLocaleValue(locale: string | undefined): Promise { +- if (!(await this.validateLocaleFile())) { +- return false; +- } + await this.jsonEditingService.write(this.environmentService.argvResource, [{ path: ['locale'], value: locale }], true); + return true; + } diff --git a/patches/local-storage.diff b/patches/local-storage.diff index 90c24abe7..25d9c3d9f 100644 --- a/patches/local-storage.diff +++ b/patches/local-storage.diff @@ -23,7 +23,8 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts @@ -332,6 +332,7 @@ export class WebClientServer { const workbenchWebConfiguration = { remoteAuthority, - webviewEndpoint: vscodeBase + this._staticRoute + '/out/vs/workbench/contrib/webview/browser/pre', + serverBasePath: basePath, + webviewEndpoint: staticRoute + '/out/vs/workbench/contrib/webview/browser/pre', + userDataPath: this._environmentService.userDataPath, _wrapWebWorkerExtHostInIframe, developmentOptions: { enableSmokeTestDriver: this._environmentService.args['enable-smoke-test-driver'] ? true : undefined, logLevel: this._logService.getLevel() }, diff --git a/patches/post-startup-notifications.patch b/patches/post-startup-notifications.patch new file mode 100644 index 000000000..d14eaf8bc --- /dev/null +++ b/patches/post-startup-notifications.patch @@ -0,0 +1,808 @@ +Index: sagemaker-code-editor/vscode/extensions/post-startup-notifications/.vscode/extensions.json +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/post-startup-notifications/.vscode/extensions.json +@@ -0,0 +1,5 @@ ++{ ++ // See http://go.microsoft.com/fwlink/?LinkId=827846 ++ // for the documentation about the extensions.json format ++ "recommendations": ["dbaeumer.vscode-eslint", "amodio.tsl-problem-matcher", "ms-vscode.extension-test-runner"] ++} +Index: sagemaker-code-editor/vscode/extensions/post-startup-notifications/.vscode/launch.json +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/post-startup-notifications/.vscode/launch.json +@@ -0,0 +1,21 @@ ++// A launch configuration that compiles the extension and then opens it inside a new window ++// Use IntelliSense to learn about possible attributes. ++// Hover to view descriptions of existing attributes. ++// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 ++{ ++ "version": "0.2.0", ++ "configurations": [ ++ { ++ "name": "Run Extension", ++ "type": "extensionHost", ++ "request": "launch", ++ "args": [ ++ "--extensionDevelopmentPath=${workspaceFolder}" ++ ], ++ "outFiles": [ ++ "${workspaceFolder}/dist/**/*.js" ++ ], ++ "preLaunchTask": "${defaultBuildTask}" ++ } ++ ] ++} +\ No newline at end of file +Index: sagemaker-code-editor/vscode/extensions/post-startup-notifications/.vscode/settings.json +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/post-startup-notifications/.vscode/settings.json +@@ -0,0 +1,13 @@ ++// Place your settings in this file to overwrite default and user settings. ++{ ++ "files.exclude": { ++ "out": false, // set this to true to hide the "out" folder with the compiled JS files ++ "dist": false // set this to true to hide the "dist" folder with the compiled JS files ++ }, ++ "search.exclude": { ++ "out": true, // set this to false to include "out" folder in search results ++ "dist": true // set this to false to include "dist" folder in search results ++ }, ++ // Turn off tsc task auto detection since we have the necessary tasks as npm scripts ++ "typescript.tsc.autoDetect": "off" ++} +\ No newline at end of file +Index: sagemaker-code-editor/vscode/extensions/post-startup-notifications/.vscode/tasks.json +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/post-startup-notifications/.vscode/tasks.json +@@ -0,0 +1,40 @@ ++// See https://go.microsoft.com/fwlink/?LinkId=733558 ++// for the documentation about the tasks.json format ++{ ++ "version": "2.0.0", ++ "tasks": [ ++ { ++ "type": "npm", ++ "script": "watch", ++ "problemMatcher": "$ts-webpack-watch", ++ "isBackground": true, ++ "presentation": { ++ "reveal": "never", ++ "group": "watchers" ++ }, ++ "group": { ++ "kind": "build", ++ "isDefault": true ++ } ++ }, ++ { ++ "type": "npm", ++ "script": "watch-tests", ++ "problemMatcher": "$tsc-watch", ++ "isBackground": true, ++ "presentation": { ++ "reveal": "never", ++ "group": "watchers" ++ }, ++ "group": "build" ++ }, ++ { ++ "label": "tasks: watch-tests", ++ "dependsOn": [ ++ "npm: watch", ++ "npm: watch-tests" ++ ], ++ "problemMatcher": [] ++ } ++ ] ++} +\ No newline at end of file +Index: sagemaker-code-editor/vscode/extensions/post-startup-notifications/.vscodeignore +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/post-startup-notifications/.vscodeignore +@@ -0,0 +1,14 @@ ++.vscode/** ++.vscode-test/** ++out/** ++node_modules/** ++src/** ++.gitignore ++.yarnrc ++webpack.config.js ++vsc-extension-quickstart.md ++**/tsconfig.json ++**/eslint.config.mjs ++**/*.map ++**/*.ts ++**/.vscode-test.* +Index: sagemaker-code-editor/vscode/extensions/post-startup-notifications/CHANGELOG.md +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/post-startup-notifications/CHANGELOG.md +@@ -0,0 +1,9 @@ ++# Change Log ++ ++All notable changes to the "post-startup-notifications" extension will be documented in this file. ++ ++Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. ++ ++## [Unreleased] ++ ++- Initial release +\ No newline at end of file +Index: sagemaker-code-editor/vscode/extensions/post-startup-notifications/README.md +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/post-startup-notifications/README.md +@@ -0,0 +1,4 @@ ++# post-startup-notifications README ++ ++This extension monitors post startup script execution and notifies users on success/failure. ++ +Index: sagemaker-code-editor/vscode/extensions/post-startup-notifications/eslint.config.mjs +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/post-startup-notifications/eslint.config.mjs +@@ -0,0 +1,28 @@ ++import typescriptEslint from "@typescript-eslint/eslint-plugin"; ++import tsParser from "@typescript-eslint/parser"; ++ ++export default [{ ++ files: ["**/*.ts"], ++}, { ++ plugins: { ++ "@typescript-eslint": typescriptEslint, ++ }, ++ ++ languageOptions: { ++ parser: tsParser, ++ ecmaVersion: 2022, ++ sourceType: "module", ++ }, ++ ++ rules: { ++ "@typescript-eslint/naming-convention": ["warn", { ++ selector: "import", ++ format: ["camelCase", "PascalCase"], ++ }], ++ ++ curly: "warn", ++ eqeqeq: "warn", ++ "no-throw-literal": "warn", ++ semi: "warn", ++ }, ++}]; +\ No newline at end of file +Index: sagemaker-code-editor/vscode/extensions/post-startup-notifications/package.json +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/post-startup-notifications/package.json +@@ -0,0 +1,57 @@ ++{ ++ "name": "post-startup-notifications", ++ "displayName": "post-startup-notifications", ++ "description": "Extension for surfacing post startup script status notifications to users", ++ "version": "0.0.1", ++ "publisher": "sagemaker", ++ "license": "MIT", ++ "engines": { ++ "vscode": "^1.98.0" ++ }, ++ "categories": [ ++ "Other" ++ ], ++ "activationEvents": [ ++ "*" ++ ], ++ "main": "./dist/extension.js", ++ "contributes": { ++ "commands": [] ++ }, ++ "scripts": { ++ "test": "jest", ++ "compile": "gulp compile-extension:post-startup-notifications", ++ "watch": "npm run build-preview && gulp watch-extension:post-startup-notifications", ++ "vscode:prepublish": "npm run build-ext", ++ "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:post-startup-notifications ./tsconfig.json" ++ }, ++ "jest": { ++ "preset": "ts-jest", ++ "testEnvironment": "node", ++ "moduleFileExtensions": [ ++ "ts", ++ "js" ++ ] ++ }, ++ "devDependencies": { ++ "@types/jest": "^29.5.14", ++ "@types/mocha": "^10.0.10", ++ "@types/node": "20.x", ++ "@types/vscode": "^1.98.0", ++ "@typescript-eslint/eslint-plugin": "^8.25.0", ++ "@typescript-eslint/parser": "^8.25.0", ++ "@vscode/test-cli": "^0.0.10", ++ "@vscode/test-electron": "^2.4.1", ++ "eslint": "^9.21.0", ++ "jest": "^29.7.0", ++ "mocha": "^11.1.0", ++ "ts-jest": "^29.3.0", ++ "ts-loader": "^9.5.2", ++ "typescript": "^5.7.3", ++ "webpack": "^5.98.0", ++ "webpack-cli": "^6.0.1" ++ }, ++ "dependencies": { ++ "chokidar": "^4.0.3" ++ } ++} +\ No newline at end of file +Index: sagemaker-code-editor/vscode/extensions/post-startup-notifications/src/constant.ts +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/post-startup-notifications/src/constant.ts +@@ -0,0 +1,3 @@ ++export const POST_START_UP_STATUS_FILE = '/tmp/.post-startup-status.json'; ++export const SERVICE_NAME_ENV_VALUE = 'SageMakerUnifiedStudio'; ++export const SERVICE_NAME_ENV_KEY = 'SERVICE_NAME'; +\ No newline at end of file +Index: sagemaker-code-editor/vscode/extensions/post-startup-notifications/src/extension.ts +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/post-startup-notifications/src/extension.ts +@@ -0,0 +1,117 @@ ++import * as vscode from 'vscode'; ++import * as fs from 'fs'; ++import { POST_START_UP_STATUS_FILE, SERVICE_NAME_ENV_KEY, SERVICE_NAME_ENV_VALUE } from './constant'; ++import { StatusFile } from './types'; ++import * as chokidar from 'chokidar'; ++ ++// Simple method to check if user has seen a notification ++function hasUserSeen(context: vscode.ExtensionContext, notificationId: string): boolean { ++ return context.globalState.get(`notification_seen_${notificationId}`) === true; ++} ++ ++// Simple method to mark notification as seen ++function markAsSeen(context: vscode.ExtensionContext, notificationId: string): void { ++ context.globalState.update(`notification_seen_${notificationId}`, true); ++} ++ ++let previousStatus: string | undefined; ++let watcher: chokidar.FSWatcher; ++let outputChannel: vscode.OutputChannel; ++ ++export function activate(context: vscode.ExtensionContext) { ++ // Check if in SageMaker Unified Studio ++ const envValue = process.env[SERVICE_NAME_ENV_KEY]; ++ ++ if (!envValue || envValue !== SERVICE_NAME_ENV_VALUE) { ++ return; ++ } ++ ++ outputChannel = vscode.window.createOutputChannel('SageMaker Unified Studio Post Startup Notifications'); ++ ++ // Show Q CLI notification if user hasn't seen it before ++ showQCliNotification(context); ++ ++ try { ++ watcher = chokidar.watch(POST_START_UP_STATUS_FILE, { ++ persistent: true, ++ ignoreInitial: false, ++ awaitWriteFinish: { ++ stabilityThreshold: 2000, ++ pollInterval: 100 ++ } ++ }); ++ ++ watcher.on('add', (path) => { ++ processStatusFile(); ++ }).on('change', (path) => { ++ processStatusFile(); ++ }).on('unlink', (path) => { ++ outputChannel.appendLine(`File ${path} has been removed`); ++ }); ++ ++ } catch (error: any) { ++ outputChannel.appendLine(`Error setting up file watcher: ${error}`); ++ } ++} ++ ++function processStatusFile() { ++ try { ++ const content = fs.readFileSync(POST_START_UP_STATUS_FILE, 'utf8'); ++ const statusData: StatusFile = JSON.parse(content); ++ ++ // Only show message if status has changed ++ if (statusData.status && statusData.status !== previousStatus) { ++ previousStatus = statusData.status; ++ ++ if (statusData.message) { ++ switch (statusData.status.toLowerCase()) { ++ case 'error': ++ vscode.window.showErrorMessage(statusData.message); ++ break; ++ case 'in-progress': ++ default: ++ vscode.window.showInformationMessage(statusData.message); ++ } ++ } ++ } ++ } catch (error: any) { ++ if (error.code !== 'ENOENT') { ++ outputChannel.appendLine(`Error processing status file: ${error.message}`); ++ } ++ } ++}; ++ ++// Show Q CLI notification if user hasn't seen it before ++function showQCliNotification(context: vscode.ExtensionContext): void { ++ const notificationId = 'smus_q_cli_notification'; ++ const message = 'The Amazon Q Command Line Interface (CLI) is installed. You can now access AI-powered assistance in your terminal.'; ++ const link = 'https://docs.aws.amazon.com/sagemaker-unified-studio/latest/userguide/q-actions.html'; ++ const linkLabel = 'Learn More'; ++ ++ if (!hasUserSeen(context, notificationId)) { ++ outputChannel.appendLine("User has not seen the notification") ++ // Show notification with Learn More button ++ vscode.window.showInformationMessage( ++ message, ++ { modal: false }, ++ { title: linkLabel, isCloseAffordance: false } ++ ).then((selection) => { ++ if (selection && selection.title === linkLabel) { ++ vscode.env.openExternal(vscode.Uri.parse(link)); ++ } ++ ++ // Mark as seen regardless of which button was clicked ++ markAsSeen(context, notificationId); ++ }); ++ } ++} ++ ++export function deactivate() { ++ if (watcher) { ++ watcher.close(); ++ } ++ outputChannel.appendLine('Status monitor deactivated'); ++ if (outputChannel) { ++ outputChannel.dispose(); ++ } ++} +\ No newline at end of file +Index: sagemaker-code-editor/vscode/extensions/post-startup-notifications/src/test/extension.test.ts +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/post-startup-notifications/src/test/extension.test.ts +@@ -0,0 +1,267 @@ ++import * as vscode from 'vscode'; ++import * as fs from 'fs'; ++import * as chokidar from 'chokidar'; ++import { activate, deactivate } from '../extension'; ++import { POST_START_UP_STATUS_FILE, SERVICE_NAME_ENV_KEY, SERVICE_NAME_ENV_VALUE } from '../constant'; ++ ++type MockCall = [string, (path: string) => void]; ++ ++interface MockFSWatcher extends chokidar.FSWatcher { ++ on: jest.Mock; ++ close: jest.Mock; ++} ++ ++// Mocks setup ++jest.mock('vscode', () => ({ ++ window: { ++ showErrorMessage: jest.fn(), ++ showInformationMessage: jest.fn().mockReturnValue(Promise.resolve()), ++ createOutputChannel: jest.fn() ++ }, ++ env: { ++ openExternal: jest.fn() ++ }, ++ Uri: { ++ parse: jest.fn(url => ({ toString: () => url })) ++ } ++})); ++ ++jest.mock('fs'); ++jest.mock('chokidar'); ++ ++describe('SageMaker Unified Studio Extension Tests', () => { ++ let mockContext: vscode.ExtensionContext; ++ let mockWatcher: MockFSWatcher; ++ let mockOutputChannel: vscode.OutputChannel; ++ ++ beforeEach(() => { ++ // Reset mocks ++ jest.resetAllMocks(); ++ ++ // Setup context with globalState for storage ++ mockContext = { ++ subscriptions: [], ++ globalState: { ++ get: jest.fn(), ++ update: jest.fn(), ++ keys: jest.fn().mockReturnValue([]) ++ } ++ } as any; ++ ++ // Setup watcher ++ mockWatcher = { ++ on: jest.fn().mockReturnThis(), ++ close: jest.fn() ++ } as any; ++ ++ mockOutputChannel = { ++ appendLine: jest.fn(), ++ dispose: jest.fn() ++ } as any; ++ ++ (chokidar.watch as jest.Mock).mockReturnValue(mockWatcher); ++ (vscode.window.createOutputChannel as jest.Mock).mockReturnValue(mockOutputChannel); ++ process.env[SERVICE_NAME_ENV_KEY] = SERVICE_NAME_ENV_VALUE; ++ }); ++ ++ // Helper function to get watcher callbacks ++ const getWatcherCallback = (eventType: string): ((path: string) => void) => { ++ const call = mockWatcher.on.mock.calls.find( ++ (call: MockCall) => call[0] === eventType ++ ); ++ return call ? call[1] : jest.fn(); ++ }; ++ ++ // Helper function to simulate file content ++ const simulateFileContent = (content: object): void => { ++ (fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(content)); ++ }; ++ ++ describe('Activation Tests', () => { ++ test('should not activate outside SageMaker environment', () => { ++ process.env[SERVICE_NAME_ENV_KEY] = 'wrong-value'; ++ activate(mockContext); ++ expect(vscode.window.createOutputChannel).not.toHaveBeenCalled(); ++ }); ++ ++ test('should initialize properly in SageMaker environment', () => { ++ activate(mockContext); ++ expect(vscode.window.createOutputChannel).toHaveBeenCalledWith( ++ 'SageMaker Unified Studio Post Startup Notifications' ++ ); ++ expect(chokidar.watch).toHaveBeenCalledWith( ++ POST_START_UP_STATUS_FILE, ++ expect.objectContaining({ ++ persistent: true, ++ ignoreInitial: false ++ }) ++ ); ++ }); ++ ++ test('should handle watcher setup errors', () => { ++ const error = new Error('Setup error'); ++ (chokidar.watch as jest.Mock).mockImplementation(() => { throw error; }); ++ activate(mockContext); ++ expect(mockOutputChannel.appendLine).toHaveBeenCalled(); ++ }); ++ }); ++ ++ describe('File Processing Tests', () => { ++ test('should handle error status', () => { ++ simulateFileContent({ ++ status: 'error', ++ message: 'Test error message' ++ }); ++ ++ activate(mockContext); ++ getWatcherCallback('add')('test-path'); ++ ++ expect(vscode.window.showErrorMessage).toHaveBeenCalledWith('Test error message'); ++ }); ++ ++ test('should handle in-progress status', () => { ++ simulateFileContent({ ++ status: 'in-progress', ++ message: 'Processing message' ++ }); ++ ++ activate(mockContext); ++ getWatcherCallback('add')('test-path'); ++ ++ expect(vscode.window.showInformationMessage).toHaveBeenCalledWith('Processing message'); ++ }); ++ ++ test('should not show message for unchanged status', () => { ++ simulateFileContent({ ++ status: 'error', ++ message: 'Error message' ++ }); ++ ++ activate(mockContext); ++ const addCallback = getWatcherCallback('add'); ++ addCallback('test-path'); ++ expect(vscode.window.showErrorMessage).toHaveBeenCalledTimes(1); ++ ++ addCallback('test-path'); ++ expect(vscode.window.showErrorMessage).toHaveBeenCalledTimes(1); ++ }); ++ ++ test('should handle file removal', () => { ++ activate(mockContext); ++ getWatcherCallback('unlink')('test-path'); ++ expect(mockOutputChannel.appendLine).toHaveBeenCalledWith('File test-path has been removed'); ++ }); ++ }); ++ ++ describe('Error Handling Tests', () => { ++ test('should handle invalid JSON', () => { ++ (fs.readFileSync as jest.Mock).mockReturnValue('invalid json'); ++ ++ activate(mockContext); ++ getWatcherCallback('add')('test-path'); ++ ++ expect(mockOutputChannel.appendLine).toHaveBeenCalledWith( ++ expect.stringContaining('Error processing status file') ++ ); ++ }); ++ ++ test('should handle file read errors', () => { ++ (fs.readFileSync as jest.Mock).mockImplementation(() => { ++ throw new Error('Read error'); ++ }); ++ ++ activate(mockContext); ++ getWatcherCallback('add')('test-path'); ++ ++ expect(mockOutputChannel.appendLine).toHaveBeenCalledWith( ++ expect.stringContaining('Error processing status file') ++ ); ++ }); ++ ++ test('should ignore ENOENT errors', () => { ++ const error = new Error('File not found'); ++ (error as any).code = 'ENOENT'; ++ (fs.readFileSync as jest.Mock).mockImplementation(() => { ++ throw error; ++ }); ++ ++ activate(mockContext); ++ getWatcherCallback('add')('test-path'); ++ ++ expect(mockOutputChannel.appendLine).not.toHaveBeenCalled(); ++ }); ++ ++ test('should handle missing status or message', () => { ++ simulateFileContent({}); ++ ++ activate(mockContext); ++ getWatcherCallback('add')('test-path'); ++ ++ expect(vscode.window.showErrorMessage).not.toHaveBeenCalled(); ++ expect(vscode.window.showInformationMessage).not.toHaveBeenCalled(); ++ }); ++ }); ++ ++ describe('Q CLI Notification Tests', () => { ++ test('should show Q CLI notification with Learn More button', () => { ++ // Set up globalState to simulate first-time user ++ (mockContext.globalState.get as jest.Mock).mockReturnValue(undefined); ++ ++ activate(mockContext); ++ ++ // Verify notification is shown with correct message and button ++ expect(vscode.window.showInformationMessage).toHaveBeenCalledWith( ++ 'The Amazon Q Command Line Interface (CLI) is installed. You can now access AI-powered assistance in your terminal.', ++ { modal: false }, ++ { title: 'Learn More', isCloseAffordance: false } ++ ); ++ }); ++ ++ test('should open documentation when Learn More is clicked', async () => { ++ // Set up globalState to simulate first-time user ++ (mockContext.globalState.get as jest.Mock).mockReturnValue(undefined); ++ ++ // Mock the user clicking "Learn More" ++ const mockSelection = { title: 'Learn More' }; ++ (vscode.window.showInformationMessage as jest.Mock).mockReturnValue(Promise.resolve(mockSelection)); ++ ++ activate(mockContext); ++ ++ // Wait for the promise to resolve ++ await new Promise(process.nextTick); ++ ++ // Verify the documentation link is opened ++ expect(vscode.env.openExternal).toHaveBeenCalledWith( ++ expect.objectContaining({ ++ toString: expect.any(Function) ++ }) ++ ); ++ ++ // Verify notification is marked as seen ++ expect(mockContext.globalState.update).toHaveBeenCalledWith( ++ 'notification_seen_smus_q_cli_notification', ++ true ++ ); ++ }); ++ ++ test('should not show notification if already seen', () => { ++ // Set up globalState to simulate returning user who has seen notification ++ (mockContext.globalState.get as jest.Mock).mockReturnValue(true); ++ ++ activate(mockContext); ++ ++ // Verify notification is not shown again ++ expect(vscode.window.showInformationMessage).not.toHaveBeenCalled(); ++ }); ++ }); ++ ++ describe('Deactivation Tests', () => { ++ test('should cleanup resources properly', () => { ++ activate(mockContext); ++ deactivate(); ++ ++ expect(mockWatcher.close).toHaveBeenCalled(); ++ expect(mockOutputChannel.dispose).toHaveBeenCalled(); ++ }); ++ }); ++}); +\ No newline at end of file +Index: sagemaker-code-editor/vscode/extensions/post-startup-notifications/src/types.ts +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/post-startup-notifications/src/types.ts +@@ -0,0 +1,6 @@ ++export interface StatusFile { ++ status: string; ++ message: string; ++ link: string; ++ label: string; ++} +\ No newline at end of file +Index: sagemaker-code-editor/vscode/extensions/post-startup-notifications/tsconfig.json +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/post-startup-notifications/tsconfig.json +@@ -0,0 +1,17 @@ ++{ ++ "compilerOptions": { ++ "module": "Node16", ++ "target": "ES2022", ++ "lib": [ ++ "ES2022" ++ ], ++ "sourceMap": true, ++ "rootDir": "src", ++ "strict": true, /* enable all strict type-checking options */ ++ "isolatedModules": true ++ /* Additional Checks */ ++ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ ++ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ ++ // "noUnusedParameters": true, /* Report errors on unused parameters. */ ++ } ++} +\ No newline at end of file +Index: sagemaker-code-editor/vscode/extensions/post-startup-notifications/webpack.config.js +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/post-startup-notifications/webpack.config.js +@@ -0,0 +1,48 @@ ++//@ts-check ++ ++'use strict'; ++ ++const path = require('path'); ++ ++//@ts-check ++/** @typedef {import('webpack').Configuration} WebpackConfig **/ ++ ++/** @type WebpackConfig */ ++const extensionConfig = { ++ target: 'node', // VS Code extensions run in a Node.js-context ๐Ÿ“– -> https://webpack.js.org/configuration/node/ ++ mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production') ++ ++ entry: './src/extension.ts', // the entry point of this extension, ๐Ÿ“– -> https://webpack.js.org/configuration/entry-context/ ++ output: { ++ // the bundle is stored in the 'dist' folder (check package.json), ๐Ÿ“– -> https://webpack.js.org/configuration/output/ ++ path: path.resolve(__dirname, 'dist'), ++ filename: 'extension.js', ++ libraryTarget: 'commonjs2' ++ }, ++ externals: { ++ vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, ๐Ÿ“– -> https://webpack.js.org/configuration/externals/ ++ // modules added here also need to be added in the .vscodeignore file ++ }, ++ resolve: { ++ // support reading TypeScript and JavaScript files, ๐Ÿ“– -> https://github.com/TypeStrong/ts-loader ++ extensions: ['.ts', '.js'] ++ }, ++ module: { ++ rules: [ ++ { ++ test: /\.ts$/, ++ exclude: /node_modules/, ++ use: [ ++ { ++ loader: 'ts-loader' ++ } ++ ] ++ } ++ ] ++ }, ++ devtool: 'nosources-source-map', ++ infrastructureLogging: { ++ level: "log", // enables logging required for problem matchers ++ }, ++}; ++module.exports = [extensionConfig]; +\ No newline at end of file +Index: sagemaker-code-editor/vscode/build/npm/dirs.js +=================================================================== +--- sagemaker-code-editor.orig/vscode/build/npm/dirs.js ++++ sagemaker-code-editor/vscode/build/npm/dirs.js +@@ -44,6 +44,7 @@ const dirs = [ + 'extensions/sagemaker-terminal-crash-mitigation', + 'extensions/sagemaker-open-notebook-extension', + 'extensions/sagemaker-ui-dark-theme', ++ 'extensions/post-startup-notifications', + 'extensions/search-result', + 'extensions/simple-browser', + 'extensions/tunnel-forwarding', +Index: sagemaker-code-editor/vscode/build/gulpfile.extensions.js +=================================================================== +--- sagemaker-code-editor.orig/vscode/build/gulpfile.extensions.js ++++ sagemaker-code-editor/vscode/build/gulpfile.extensions.js +@@ -65,6 +65,7 @@ const compilations = [ + 'extensions/sagemaker-terminal-crash-mitigation/tsconfig.json', + 'extensions/sagemaker-open-notebook-extension/tsconfig.json', + 'extensions/sagemaker-ui-dark-theme/tsconfig.json', ++ 'extensions/post-startup-notifications/tsconfig.json', + 'extensions/tunnel-forwarding/tsconfig.json', + 'extensions/typescript-language-features/test-workspace/tsconfig.json', + 'extensions/typescript-language-features/web/tsconfig.json', +Index: sagemaker-code-editor/vscode/extensions/post-startup-notifications/extension-browser.webpack.config.js +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/post-startup-notifications/extension-browser.webpack.config.js +@@ -0,0 +1,17 @@ ++/*--------------------------------------------------------------------------------------------- ++ * Copyright Amazon.com Inc. or its affiliates. All rights reserved. ++ * Licensed under the MIT License. See License.txt in the project root for license information. ++ *--------------------------------------------------------------------------------------------*/ ++ ++//@ts-check ++ ++'use strict'; ++ ++const withBrowserDefaults = require('../shared.webpack.config').browser; ++ ++module.exports = withBrowserDefaults({ ++ context: __dirname, ++ entry: { ++ extension: './src/extension.ts' ++ }, ++}); +Index: sagemaker-code-editor/vscode/extensions/post-startup-notifications/extension.webpack.config.js +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/post-startup-notifications/extension.webpack.config.js +@@ -0,0 +1,20 @@ ++/*--------------------------------------------------------------------------------------------- ++ * Copyright Amazon.com Inc. or its affiliates. All rights reserved. ++ * Licensed under the MIT License. See License.txt in the project root for license information. ++ *--------------------------------------------------------------------------------------------*/ ++ ++//@ts-check ++ ++'use strict'; ++ ++const withDefaults = require('../shared.webpack.config'); ++ ++module.exports = withDefaults({ ++ context: __dirname, ++ resolve: { ++ mainFields: ['module', 'main'] ++ }, ++ entry: { ++ extension: './src/extension.ts', ++ } ++}); +\ No newline at end of file diff --git a/patches/sagemaker-extension-smus-support.patch b/patches/sagemaker-extension-smus-support.patch new file mode 100644 index 000000000..303039bbc --- /dev/null +++ b/patches/sagemaker-extension-smus-support.patch @@ -0,0 +1,182 @@ +Index: sagemaker-code-editor/vscode/extensions/sagemaker-extension/src/constant.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/extensions/sagemaker-extension/src/constant.ts ++++ sagemaker-code-editor/vscode/extensions/sagemaker-extension/src/constant.ts +@@ -27,6 +27,10 @@ export const FIVE_MINUTES_INTERVAL_MILLI + + export const SAGEMAKER_METADATA_PATH = '/opt/ml/metadata/resource-metadata.json'; + ++// Service name identifier for SageMaker Unified Studio ++export const SMUS_SERVICE_NAME = 'SageMakerUnifiedStudio'; ++export const SERVICE_NAME_ENV_VAR = 'SERVICE_NAME'; ++ + export class SagemakerCookie { + authMode: string + expiryTime: number +@@ -56,6 +60,11 @@ export class SagemakerResourceMetadata { + ResourceArn?: string + ResourceName?: string + AppImageVersion?: string ++ AdditionalMetadata?: { ++ DataZoneDomainId?: string ++ DataZoneProjectId?: string ++ DataZoneDomainRegion?: string ++ } + }; + export function isSSOMode(cookie: SagemakerCookie) { + return (cookie.authMode === AUTH_MODE.SSO) +@@ -69,4 +78,35 @@ export function getExpiryTime(cookie: Sa + } else { + return -1; + } +-} +\ No newline at end of file ++} ++ ++/** ++ * Constructs the SMUS portal URL using domain, region, and project information ++ * Returns null if not in SMUS environment or if required fields are missing ++ */ ++export const getSmusVscodePortalUrl = (metadata: SagemakerResourceMetadata | null): string | null => { ++ if (process.env[SERVICE_NAME_ENV_VAR] !== SMUS_SERVICE_NAME) { ++ return null; ++ } ++ ++ if (!metadata || !metadata.AdditionalMetadata) { ++ // fail silently not to block users ++ console.error('[SMUS] Metadata is undefined or null'); ++ return null; ++ } ++ ++ const { DataZoneDomainId, DataZoneDomainRegion, DataZoneProjectId } = metadata.AdditionalMetadata; ++ ++ if (!DataZoneDomainId || !DataZoneDomainRegion || !DataZoneProjectId) { ++ // fail silently not to block users ++ // TODO: add monitoring to detect such cases ++ console.error('[SMUS] Required fields missing in metadata:', { ++ DataZoneDomainId: !!DataZoneDomainId, ++ DataZoneDomainRegion: !!DataZoneDomainRegion, ++ DataZoneProjectId: !!DataZoneProjectId ++ }); ++ return null; ++ } ++ ++ return `https://${DataZoneDomainId}.sagemaker.${DataZoneDomainRegion}.on.aws/projects/${DataZoneProjectId}/overview`; ++} +Index: sagemaker-code-editor/vscode/extensions/sagemaker-extension/src/extension.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/extensions/sagemaker-extension/src/extension.ts ++++ sagemaker-code-editor/vscode/extensions/sagemaker-extension/src/extension.ts +@@ -11,7 +11,8 @@ import { + WARNING_BUTTON_SAVE_AND_RENEW_SESSION, + SagemakerCookie, + SagemakerResourceMetadata, +- getExpiryTime ++ getExpiryTime, ++ getSmusVscodePortalUrl + } from "./constant"; + import * as console from "console"; + +@@ -19,6 +20,24 @@ import * as console from "console"; + const PARSE_SAGEMAKER_COOKIE_COMMAND = 'sagemaker.parseCookies'; + const ENABLE_AUTO_UPDATE_COMMAND = 'workbench.extensions.action.enableAutoUpdate'; + ++// Global redirect URL for SMUS environment ++let smusRedirectUrl: string | null = null; ++ ++function fetchMetadata(): SagemakerResourceMetadata | null { ++ try { ++ const data = fs.readFileSync(SAGEMAKER_METADATA_PATH, 'utf-8'); ++ return JSON.parse(data) as SagemakerResourceMetadata; ++ } catch (error) { ++ // fail silently not to block users ++ console.error('Error reading metadata file:', error); ++ return null; ++ } ++} ++ ++function initializeSmusRedirectUrl() { ++ smusRedirectUrl = getSmusVscodePortalUrl(fetchMetadata()); ++} ++ + function showWarningDialog() { + vscode.commands.executeCommand(PARSE_SAGEMAKER_COOKIE_COMMAND).then(response => { + +@@ -59,11 +78,12 @@ function showWarningDialog() { + } + + function signInError(sagemakerCookie: SagemakerCookie) { ++ const redirectUrl = getRedirectUrl(sagemakerCookie); + // The session has expired + SessionWarning.signInWarning(sagemakerCookie) + .then((selection) => { + if (selection === SIGN_IN_BUTTON) { +- vscode.env.openExternal(vscode.Uri.parse(sagemakerCookie.redirectURL)); ++ vscode.env.openExternal(vscode.Uri.parse(redirectUrl)); + } + }); + } +@@ -94,32 +114,21 @@ function saveWorkspace() { + }); + } + function renewSession(sagemakerCookie: SagemakerCookie) { ++ const redirectUrl = getRedirectUrl(sagemakerCookie); + // TODO: Log and trigger a Signin +- vscode.env.openExternal(vscode.Uri.parse(sagemakerCookie.redirectURL)); ++ vscode.env.openExternal(vscode.Uri.parse(redirectUrl)); + // Trigger the function to show the warning again after 5 minutes again to validate. + setTimeout(showWarningDialog, FIVE_MINUTES_INTERVAL_MILLIS); + } + + function updateStatusItemWithMetadata(context: vscode.ExtensionContext) { +- fs.readFile(SAGEMAKER_METADATA_PATH, 'utf-8', (err, data) => { +- if (err) { +- // fail silently not to block users +- } else { +- try { +- const jsonData = JSON.parse(data) as SagemakerResourceMetadata; +- const spaceName = jsonData.SpaceName; +- +- if (spaceName != null) { +- let spaceNameStatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100); +- spaceNameStatusBarItem.text = `Space: ${spaceName}`; +- spaceNameStatusBarItem.show(); +- context.subscriptions.push(spaceNameStatusBarItem); +- } +- } catch (jsonError) { +- // fail silently not to block users +- } +- } +- }); ++ const metadata = fetchMetadata(); ++ if (metadata?.SpaceName) { ++ let spaceNameStatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100); ++ spaceNameStatusBarItem.text = `Space: ${metadata.SpaceName}`; ++ spaceNameStatusBarItem.show(); ++ context.subscriptions.push(spaceNameStatusBarItem); ++ } + } + + // Render warning message regarding auto upgrade disabled +@@ -158,6 +167,9 @@ export function activate(context: vscode + // TODO: log activation of extension + console.log('Activating Sagemaker Extension...'); + ++ // First set smusRedirectUrl if we are in SMUS environment ++ initializeSmusRedirectUrl(); ++ + // execute the get cookie command and save the data to cookies + vscode.commands.executeCommand(PARSE_SAGEMAKER_COOKIE_COMMAND).then(r => { + +@@ -170,3 +182,11 @@ export function activate(context: vscode + // render warning message regarding auto upgrade disabled + renderExtensionAutoUpgradeDisabledNotification(); + } ++ ++/** ++ * Returns the appropriate redirect URL based on the environment ++ * Uses SMUS URL if available, falls back to original redirect URL ++ */ ++function getRedirectUrl(sagemakerCookie: SagemakerCookie): string { ++ return smusRedirectUrl || sagemakerCookie.redirectURL; ++} diff --git a/patches/sagemaker-extension.diff b/patches/sagemaker-extension.diff index 998215f24..8e957d9f6 100644 --- a/patches/sagemaker-extension.diff +++ b/patches/sagemaker-extension.diff @@ -2,7 +2,7 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extension/src/extension =================================================================== --- /dev/null +++ sagemaker-code-editor/vscode/extensions/sagemaker-extension/src/extension.ts -@@ -0,0 +1,137 @@ +@@ -0,0 +1,172 @@ +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import { SessionWarning } from "./sessionWarning"; @@ -22,6 +22,7 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extension/src/extension + + +const PARSE_SAGEMAKER_COOKIE_COMMAND = 'sagemaker.parseCookies'; ++const ENABLE_AUTO_UPDATE_COMMAND = 'workbench.extensions.action.enableAutoUpdate'; + +function showWarningDialog() { + vscode.commands.executeCommand(PARSE_SAGEMAKER_COOKIE_COMMAND).then(response => { @@ -126,6 +127,37 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extension/src/extension + }); +} + ++// Render warning message regarding auto upgrade disabled ++function renderExtensionAutoUpgradeDisabledNotification() { ++ // Get current extension auto disabled config ++ const autoUpdateEnabled = vscode.workspace.getConfiguration('extensions').get('autoUpdate'); ++ ++ // Check if customer has choose to disable this notification ++ const extensionConfig = vscode.workspace.getConfiguration('sagemaker-extension'); ++ const showNotificationEnabled = extensionConfig.get('notification.extensionAutoUpdateDisabled', true); ++ ++ // Only show notification, if auto update is disabled, and customer hasn't opt-out the notification ++ if (showNotificationEnabled && autoUpdateEnabled == false) { ++ const enableAutoUpdate = 'Enable Auto Update Extensions'; ++ const doNotShowAgain = 'Do not show again'; ++ vscode.window.showInformationMessage( ++ 'Extension auto-update is disabled. This can be changed in Code Editor settings.', ++ enableAutoUpdate, ++ doNotShowAgain, ++ ).then(response => { ++ if (response === enableAutoUpdate) { ++ vscode.commands.executeCommand(ENABLE_AUTO_UPDATE_COMMAND) ++ } else if (response == doNotShowAgain) { ++ extensionConfig.update( ++ 'notification.extensionAutoUpdateDisabled', ++ false, ++ vscode.ConfigurationTarget.Global ++ ); ++ } ++ }) ++ } ++} ++ +export function activate(context: vscode.ExtensionContext) { + + // TODO: log activation of extension @@ -139,6 +171,9 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extension/src/extension + initialize(sagemakerCookie); + updateStatusItemWithMetadata(context); + }); ++ ++ // render warning message regarding auto upgrade disabled ++ renderExtensionAutoUpgradeDisabledNotification(); +} Index: sagemaker-code-editor/vscode/extensions/sagemaker-extension/src/sessionWarning.ts =================================================================== @@ -194,7 +229,7 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extension/.vscodeignore =================================================================== --- /dev/null +++ sagemaker-code-editor/vscode/extensions/sagemaker-extension/.vscodeignore -@@ -0,0 +1,12 @@ +@@ -0,0 +1,11 @@ +.vscode/** +.vscode-test/** +out/test/** @@ -205,13 +240,12 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extension/.vscodeignore +out/test/** +out/** +cgmanifest.json -+yarn.lock +preview-src/** Index: sagemaker-code-editor/vscode/extensions/sagemaker-extension/package.json =================================================================== --- /dev/null +++ sagemaker-code-editor/vscode/extensions/sagemaker-extension/package.json -@@ -0,0 +1,46 @@ +@@ -0,0 +1,52 @@ +{ + "name": "sagemaker-extension", + "displayName": "Sagemaker Extension", @@ -241,8 +275,14 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extension/package.json + "contributes": { + "configuration": { + "type": "object", -+ "title": "Sagemaker Extension", -+ "properties": {} ++ "title": "SageMaker Extension", ++ "properties": { ++ "sagemaker-extension.notification.extensionAutoUpdateDisabled": { ++ "type": "boolean", ++ "default": true, ++ "markdownDescription": "Show notification if extension auto update is disabled" ++ } ++ } + }, + "commands": [ + ] diff --git a/patches/sagemaker-extensions-sync.patch b/patches/sagemaker-extensions-sync.patch new file mode 100644 index 000000000..f53792084 --- /dev/null +++ b/patches/sagemaker-extensions-sync.patch @@ -0,0 +1,450 @@ +Index: sagemaker-code-editor/vscode/build/gulpfile.extensions.js +=================================================================== +--- sagemaker-code-editor.orig/vscode/build/gulpfile.extensions.js ++++ sagemaker-code-editor/vscode/build/gulpfile.extensions.js +@@ -62,6 +62,7 @@ const compilations = [ + 'extensions/simple-browser/tsconfig.json', + 'extensions/sagemaker-extension/tsconfig.json', + 'extensions/sagemaker-idle-extension/tsconfig.json', ++ 'extensions/sagemaker-extensions-sync/tsconfig.json', + 'extensions/sagemaker-terminal-crash-mitigation/tsconfig.json', + 'extensions/sagemaker-open-notebook-extension/tsconfig.json', + 'extensions/tunnel-forwarding/tsconfig.json', +Index: sagemaker-code-editor/vscode/build/npm/dirs.js +=================================================================== +--- sagemaker-code-editor.orig/vscode/build/npm/dirs.js ++++ sagemaker-code-editor/vscode/build/npm/dirs.js +@@ -40,6 +40,7 @@ const dirs = [ + 'extensions/php-language-features', + 'extensions/references-view', + 'extensions/sagemaker-extension', ++ 'extensions/sagemaker-extensions-sync', + 'extensions/sagemaker-idle-extension', + 'extensions/sagemaker-terminal-crash-mitigation', + 'extensions/sagemaker-open-notebook-extension', +Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/.vscodeignore +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/.vscodeignore +@@ -0,0 +1,11 @@ ++.vscode/** ++.vscode-test/** ++out/test/** ++out/** ++test/** ++src/** ++tsconfig.json ++out/test/** ++out/** ++cgmanifest.json ++preview-src/** +Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/README.md +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/README.md +@@ -0,0 +1,3 @@ ++# SageMaker Code Editor Extensions Sync ++ ++Notifies users if the extensions directory is missing pre-packaged extensions from SageMaker Distribution and give them the option to sync them. +\ No newline at end of file +Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/extension-browser.webpack.config.js +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/extension-browser.webpack.config.js +@@ -0,0 +1,17 @@ ++/*--------------------------------------------------------------------------------------------- ++ * Copyright Amazon.com Inc. or its affiliates. All rights reserved. ++ * Licensed under the MIT License. See License.txt in the project root for license information. ++ *--------------------------------------------------------------------------------------------*/ ++ ++//@ts-check ++ ++'use strict'; ++ ++const withBrowserDefaults = require('../shared.webpack.config').browser; ++ ++module.exports = withBrowserDefaults({ ++ context: __dirname, ++ entry: { ++ extension: './src/extension.ts' ++ }, ++}); +Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/extension.webpack.config.js +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/extension.webpack.config.js +@@ -0,0 +1,20 @@ ++/*--------------------------------------------------------------------------------------------- ++ * Copyright Amazon.com Inc. or its affiliates. All rights reserved. ++ * Licensed under the MIT License. See License.txt in the project root for license information. ++ *--------------------------------------------------------------------------------------------*/ ++ ++//@ts-check ++ ++'use strict'; ++ ++const withDefaults = require('../shared.webpack.config'); ++ ++module.exports = withDefaults({ ++ context: __dirname, ++ resolve: { ++ mainFields: ['module', 'main'] ++ }, ++ entry: { ++ extension: './src/extension.ts', ++ } ++}); +Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/package.json +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/package.json +@@ -0,0 +1,44 @@ ++{ ++ "name": "sagemaker-extensions-sync", ++ "displayName": "SageMaker Extensions Sync", ++ "description": "Sync pre-packaged extensions from SageMaker Distribution", ++ "extensionKind": [ ++ "workspace" ++ ], ++ "version": "1.0.0", ++ "publisher": "sagemaker", ++ "license": "MIT", ++ "engines": { ++ "vscode": "^1.70.0" ++ }, ++ "main": "./out/extension", ++ "categories": [ ++ "Other" ++ ], ++ "activationEvents": [ ++ "*" ++ ], ++ "capabilities": { ++ "virtualWorkspaces": true, ++ "untrustedWorkspaces": { ++ "supported": true ++ } ++ }, ++ "contributes": { ++ "commands": [ ++ { ++ "command": "extensions-sync.syncExtensions", ++ "title": "Sync Extensions from SageMaker Distribution", ++ "category": "Extensions Sync" ++ } ++ ] ++ }, ++ "scripts": { ++ "compile": "gulp compile-extension:sagemaker-extensions-sync", ++ "watch": "npm run build-preview && gulp watch-extension:sagemaker-extensions-sync", ++ "vscode:prepublish": "npm run build-ext", ++ "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:sagemaker-idle-extension ./tsconfig.json" ++ }, ++ "dependencies": {}, ++ "repository": {} ++} +Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/constants.ts +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/constants.ts +@@ -0,0 +1,21 @@ ++// constants ++export const PERSISTENT_VOLUME_EXTENSIONS_DIR = "/home/sagemaker-user/sagemaker-code-editor-server-data/extensions"; ++export const IMAGE_EXTENSIONS_DIR = "/opt/amazon/sagemaker/sagemaker-code-editor-server-data/extensions"; ++export const LOG_PREFIX = "[sagemaker-extensions-sync]"; ++ ++export class ExtensionInfo { ++ constructor( ++ public name: string, ++ public publisher: string, ++ public version: string, ++ public path: string | null ++ ) {} ++ ++ get identifier(): string { ++ return `${this.publisher}.${this.name}@${this.version}`; ++ } ++ ++ toString(): string { ++ return `ExtensionInfo: ${this.identifier} (${this.path})`; ++ } ++} +Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/extension.ts +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/extension.ts +@@ -0,0 +1,100 @@ ++import * as process from "process"; ++import * as vscode from 'vscode'; ++ ++import { ++ ExtensionInfo, ++ IMAGE_EXTENSIONS_DIR, ++ LOG_PREFIX, ++ PERSISTENT_VOLUME_EXTENSIONS_DIR, ++} from "./constants" ++ ++import { ++ getExtensionsFromDirectory, ++ getInstalledExtensions, ++ installExtension, ++ refreshExtensionsMetadata } from "./utils" ++ ++export async function activate() { ++ ++ // this extension will only activate within a sagemaker app ++ const isSageMakerApp = !!process.env?.SAGEMAKER_APP_TYPE_LOWERCASE; ++ if (!isSageMakerApp) { ++ return; ++ } ++ ++ // get installed extensions. this could be different from pvExtensions b/c vscode sometimes doesn't delete the assets ++ // for an old extension when uninstalling or changing versions ++ const installedExtensions = new Set(await getInstalledExtensions()); ++ console.log(`${LOG_PREFIX} Found installed extensions: `, Array.from(installedExtensions)); ++ ++ const prePackagedExtensions: ExtensionInfo[] = await getExtensionsFromDirectory(IMAGE_EXTENSIONS_DIR); ++ const prePackagedExtensionsById: Record = {}; ++ prePackagedExtensions.forEach(extension => { ++ prePackagedExtensionsById[extension.identifier] = extension; ++ }); ++ ++ console.log(`${LOG_PREFIX} Found pre-packaged extensions: `, prePackagedExtensions); ++ ++ const pvExtensions = await getExtensionsFromDirectory(PERSISTENT_VOLUME_EXTENSIONS_DIR); ++ const pvExtensionsByName: Record = {}; ++ const pvExtensionsById: Record = {}; ++ pvExtensions.forEach(extension => { ++ if (installedExtensions.has(extension.identifier)) { // only index extensions that are installed ++ pvExtensionsByName[extension.name] = extension; ++ pvExtensionsById[extension.identifier] = extension; ++ } ++ }); ++ console.log(`${LOG_PREFIX} Found installed extensions in persistent volume: `, pvExtensionsById); ++ ++ // check each pre-packaged extension, record if it is not in installed extensions or version mismatch ++ // store unsynced extensions as {identifier pre-packaged ext: currently installed version} ++ const unsyncedExtensions: Record = {} ++ prePackagedExtensions.forEach(extension => { ++ const id = extension.identifier; ++ if (!(installedExtensions.has(id))){ ++ unsyncedExtensions[id] = pvExtensionsByName[extension.name]?.version ?? null; ++ } ++ }); ++ console.log(`${LOG_PREFIX} Unsynced extensions: `, unsyncedExtensions); ++ ++ if (Object.keys(unsyncedExtensions).length !== 0) { ++ const selection = await vscode.window.showWarningMessage( ++ 'Warning: You have unsynchronized extensions from SageMaker Distribution \ ++ which could result in incompatibilities with Code Editor. Do you want to install them?', ++ "Synchronize Extensions", "Dismiss"); ++ ++ if (selection === "Synchronize Extensions") { ++ const quickPick = vscode.window.createQuickPick(); ++ quickPick.items = Object.keys(unsyncedExtensions).map(extensionId => ({ ++ label: extensionId, ++ description: unsyncedExtensions[extensionId] ? `Currently installed version: ${unsyncedExtensions[extensionId]}` : undefined, ++ })); ++ quickPick.placeholder = 'Select extensions to install'; ++ quickPick.canSelectMany = true; ++ quickPick.ignoreFocusOut = true; ++ ++ quickPick.onDidAccept(async () => { ++ const selectedExtensions = quickPick.selectedItems.map(item => item.label); ++ ++ for (const extensionId of selectedExtensions) { ++ const extensionName = prePackagedExtensionsById[extensionId].name; ++ await installExtension(prePackagedExtensionsById[extensionId], pvExtensionsByName[extensionName]); ++ } ++ await refreshExtensionsMetadata(); ++ ++ quickPick.hide(); ++ await vscode.window.showInformationMessage( ++ 'Extensions have been installed. \nWould you like to reload the window?', ++ { modal: true }, ++ 'Reload' ++ ).then(selection => { ++ if (selection === 'Reload') { ++ vscode.commands.executeCommand('workbench.action.reloadWindow'); ++ } ++ }); ++ }); ++ ++ quickPick.show(); ++ } ++ } ++} +\ No newline at end of file +Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/tsconfig.json +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/tsconfig.json +@@ -0,0 +1,10 @@ ++{ ++ "extends": "../tsconfig.base.json", ++ "compilerOptions": { ++ "outDir": "./out" ++ }, ++ "include": [ ++ "../sagemaker-extensions-sync/src/**/*", ++ "../../src/vscode-dts/vscode.d.ts" ++ ] ++} +Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/utils.ts +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/utils.ts +@@ -0,0 +1,152 @@ ++import * as fs from "fs/promises"; ++import * as path from "path"; ++import * as vscode from 'vscode'; ++import { execFile } from "child_process"; ++import { promisify } from "util"; ++ ++import { ++ ExtensionInfo, ++ LOG_PREFIX, ++ PERSISTENT_VOLUME_EXTENSIONS_DIR, ++} from "./constants" ++ ++export async function getExtensionsFromDirectory(directoryPath: string): Promise { ++ const results: ExtensionInfo[] = []; ++ try { ++ const items = await fs.readdir(directoryPath); ++ ++ for (const item of items) { ++ const itemPath = path.join(directoryPath, item); ++ try { ++ const stats = await fs.stat(itemPath); ++ ++ if (stats.isDirectory()) { ++ const packageJsonPath = path.join(itemPath, "package.json"); ++ ++ const packageData = JSON.parse(await fs.readFile(packageJsonPath, "utf8")); ++ ++ if (packageData.name && packageData.publisher && packageData.version) { ++ results.push(new ExtensionInfo( ++ packageData.name, ++ packageData.publisher, ++ packageData.version, ++ itemPath, ++ )); ++ } ++ } ++ } catch (error) { ++ // fs.stat will break on dangling simlinks. Just skip to the next file ++ console.error(`${LOG_PREFIX} Error reading package.json in ${itemPath}:`, error); ++ } ++ } ++ } catch (error) { ++ console.error(`${LOG_PREFIX} Error reading directory ${directoryPath}:`, error); ++ } ++ return results; ++} ++ ++export async function getInstalledExtensions(): Promise { ++ const command = "sagemaker-code-editor"; ++ const args = ["--list-extensions", "--show-versions", "--extensions-dir", PERSISTENT_VOLUME_EXTENSIONS_DIR]; ++ ++ const execFileAsync = promisify(execFile); ++ try { ++ const { stdout, stderr } = await execFileAsync(command, args); ++ if (stderr) { ++ throw new Error("stderr"); ++ } ++ return stdout.split("\n").filter(line => line.trim() !== ""); ++ } catch (error) { ++ console.error(`${LOG_PREFIX} Error getting list of installed extensions:`, error); ++ throw error; ++ } ++} ++ ++export async function refreshExtensionsMetadata(): Promise { ++ const metaDataFile = path.join(PERSISTENT_VOLUME_EXTENSIONS_DIR, "extensions.json"); ++ try { ++ await fs.unlink(metaDataFile); ++ } catch (error) { ++ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { ++ console.error(`${LOG_PREFIX} Error removing metadata file:`, error); ++ } ++ } ++} ++ ++export async function installExtension( ++ prePackagedExtensionInfo: ExtensionInfo, installedExtensionInfo?: ExtensionInfo | undefined ++): Promise { ++ if (installedExtensionInfo) { ++ console.log(`${LOG_PREFIX} Upgrading extension from ${installedExtensionInfo.identifier} to ${prePackagedExtensionInfo.identifier}`); ++ } else { ++ console.log(`${LOG_PREFIX} Installing extension ${prePackagedExtensionInfo.identifier}`); ++ } ++ try { ++ if (!prePackagedExtensionInfo.path) { ++ throw new Error(`Extension path missing for ${prePackagedExtensionInfo.identifier}`); ++ } ++ ++ const targetPath = path.join(PERSISTENT_VOLUME_EXTENSIONS_DIR, path.basename(prePackagedExtensionInfo.path)); ++ ++ // Remove existing symlink or directory if it exists ++ try { ++ console.log(`${LOG_PREFIX} Removing existing folder ${targetPath}`); ++ await fs.unlink(targetPath); ++ } catch (error) { ++ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { ++ console.error(`${LOG_PREFIX} Error removing existing extension:`, error); ++ throw error; ++ } ++ // if file already doesn't exist then keep going ++ } ++ ++ // Create new symlink ++ try { ++ console.log(`${LOG_PREFIX} Adding extension to persistent volume directory`); ++ await fs.symlink(prePackagedExtensionInfo.path, targetPath, 'dir'); ++ } catch (error) { ++ console.error(`${LOG_PREFIX} Error adding extension to persistent volume directory:`, error); ++ throw error; ++ } ++ ++ // Handle .obsolete file ++ const OBSOLETE_FILE = path.join(PERSISTENT_VOLUME_EXTENSIONS_DIR, '.obsolete'); ++ let obsoleteData: Record = {}; ++ ++ try { ++ const obsoleteContent = await fs.readFile(OBSOLETE_FILE, 'utf-8'); ++ console.log(`${LOG_PREFIX} .obsolete file found`); ++ obsoleteData = JSON.parse(obsoleteContent); ++ } catch (error) { ++ if ((error as NodeJS.ErrnoException).code === 'ENOENT') { ++ console.log(`${LOG_PREFIX} .obsolete file not found. Creating a new one.`); ++ } else { ++ console.warn(`${LOG_PREFIX} Error reading .obsolete file:`, error); ++ // Backup malformed file ++ const backupPath = `${OBSOLETE_FILE}.bak`; ++ await fs.rename(OBSOLETE_FILE, backupPath); ++ console.log(`${LOG_PREFIX} Backed up malformed .obsolete file to ${backupPath}`); ++ } ++ } ++ ++ if (installedExtensionInfo?.path) { ++ const obsoleteBasename = path.basename(installedExtensionInfo.path); ++ obsoleteData[obsoleteBasename] = true; ++ } ++ const obsoleteBasenamePrepackaged = path.basename(prePackagedExtensionInfo.path); ++ obsoleteData[obsoleteBasenamePrepackaged] = false; ++ ++ try { ++ console.log(`${LOG_PREFIX} Writing to .obsolete file.`); ++ await fs.writeFile(OBSOLETE_FILE, JSON.stringify(obsoleteData, null, 2)); ++ } catch (error) { ++ console.error(`${LOG_PREFIX} Error writing .obsolete file:`, error); ++ throw error; ++ } ++ ++ console.log(`${LOG_PREFIX} Installed ${prePackagedExtensionInfo.identifier}`); ++ } catch (error) { ++ vscode.window.showErrorMessage(`Could not install extension ${prePackagedExtensionInfo.identifier}`); ++ console.error(`${LOG_PREFIX} ${error}`); ++ } ++} +\ No newline at end of file diff --git a/patches/sagemaker-idle-extension.patch b/patches/sagemaker-idle-extension.patch index f42b600de..f475f6071 100644 --- a/patches/sagemaker-idle-extension.patch +++ b/patches/sagemaker-idle-extension.patch @@ -117,20 +117,11 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-idle-extension/tsconfig + "../../src/vscode-dts/vscode.d.ts" + ] +} -Index: sagemaker-code-editor/vscode/extensions/sagemaker-idle-extension/yarn.lock -=================================================================== ---- /dev/null -+++ sagemaker-code-editor/vscode/extensions/sagemaker-idle-extension/yarn.lock -@@ -0,0 +1,4 @@ -+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -+# yarn lockfile v1 -+ -+ Index: sagemaker-code-editor/vscode/extensions/sagemaker-idle-extension/.vscodeignore =================================================================== --- /dev/null +++ sagemaker-code-editor/vscode/extensions/sagemaker-idle-extension/.vscodeignore -@@ -0,0 +1,12 @@ +@@ -0,0 +1,11 @@ +.vscode/** +.vscode-test/** +out/test/** @@ -141,16 +132,15 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-idle-extension/.vscodei +out/test/** +out/** +cgmanifest.json -+yarn.lock +preview-src/** Index: sagemaker-code-editor/vscode/extensions/sagemaker-idle-extension/src/extension.ts =================================================================== --- /dev/null +++ sagemaker-code-editor/vscode/extensions/sagemaker-idle-extension/src/extension.ts -@@ -0,0 +1,116 @@ +@@ -0,0 +1,112 @@ +import * as vscode from "vscode"; +import * as fs from "fs"; -+import * as path from "path"; ++import { join } from "path"; + +let idleFilePath: string +let terminalActivityInterval: NodeJS.Timeout | undefined @@ -171,15 +161,11 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-idle-extension/src/exte + +/** + * Initializes the file path where the idle timestamp will be stored. -+ * It sets the path to a hidden file in the user's home directory. ++ * It sets the path to a hidden file in the /tmp/ directory. + */ +function initializeIdleFilePath() { -+ const homeDirectory = process.env.HOME || process.env.USERPROFILE; -+ if (!homeDirectory) { -+ console.log(`${LOG_PREFIX} Unable to determine the home directory.`); -+ return; -+ } -+ idleFilePath = path.join(homeDirectory, ".sagemaker-last-active-timestamp"); ++ const tmpDirectory = "/tmp/"; ++ idleFilePath = join(tmpDirectory, ".sagemaker-last-active-timestamp"); + + // Set initial lastActivetimestamp + updateLastActivityTimestamp() @@ -239,7 +225,7 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-idle-extension/src/exte + + const now = Date.now(); + const activityDetected = files.some((file) => { -+ const filePath = path.join("/dev/pts", file); ++ const filePath = join("/dev/pts", file); + try { + const stats = fs.statSync(filePath); + const mtime = new Date(stats.mtime).getTime(); @@ -293,65 +279,60 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts =================================================================== --- sagemaker-code-editor.orig/vscode/src/vs/server/node/webClientServer.ts +++ sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts -@@ -4,6 +4,7 @@ +@@ -3,7 +3,7 @@ + * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { createReadStream } from 'fs'; -+import {readFile } from 'fs/promises'; - import { Promises } from 'vs/base/node/pfs'; - import * as path from 'path'; +-import { createReadStream, promises } from 'fs'; ++import { createReadStream, promises, existsSync, writeFileSync } from 'fs'; import * as http from 'http'; -@@ -100,6 +101,7 @@ export class WebClientServer { - private readonly _staticRoute: string; - private readonly _callbackRoute: string; - private readonly _webExtensionRoute: string; -+ private readonly _idleRoute: string; + import * as url from 'url'; + import * as cookie from 'cookie'; +@@ -95,6 +95,7 @@ const APP_ROOT = dirname(FileAccess.asFi + const STATIC_PATH = `/static`; + const CALLBACK_PATH = `/callback`; + const WEB_EXTENSION_PATH = `/web-extension-resource`; ++const IDLE_EXTENSION_PATH = `/api/idle`; - constructor( - private readonly _connectionToken: ServerConnectionToken, -@@ -115,6 +117,7 @@ export class WebClientServer { - this._staticRoute = `${serverRootPath}/static`; - this._callbackRoute = `${serverRootPath}/callback`; - this._webExtensionRoute = `${serverRootPath}/web-extension-resource`; -+ this._idleRoute = '/api/idle'; - } + export class WebClientServer { - /** -@@ -132,6 +135,9 @@ export class WebClientServer { - if (pathname === this._basePath) { - return this._handleRoot(req, res, parsedUrl); +@@ -131,6 +132,9 @@ export class WebClientServer { + // callback support + return this._handleCallback(res); } -+ if (pathname === this._idleRoute) { ++ if (pathname === IDLE_EXTENSION_PATH) { + return this._handleIdle(req, res); + } - if (pathname === this._callbackRoute) { - // callback support - return this._handleCallback(res); -@@ -451,6 +457,27 @@ export class WebClientServer { + if (pathname.startsWith(WEB_EXTENSION_PATH) && pathname.charCodeAt(WEB_EXTENSION_PATH.length) === CharCode.Slash) { + // extension resource support + return this._handleWebExtensionResource(req, res, pathname.substring(WEB_EXTENSION_PATH.length)); +@@ -496,4 +500,29 @@ export class WebClientServer { }); return void res.end(data); } + + /** -+ * Handles API requests to retrieve the last activity timestamp. -+ */ -+ private async _handleIdle(req: http.IncomingMessage, res: http.ServerResponse): Promise { -+ try { -+ const homeDirectory = process.env.HOME || process.env.USERPROFILE; -+ if (!homeDirectory) { -+ throw new Error('Home directory not found'); -+ } -+ -+ const idleFilePath = path.join(homeDirectory, '.sagemaker-last-active-timestamp'); -+ const data = await readFile(idleFilePath, 'utf8'); -+ -+ res.statusCode = 200; -+ res.setHeader('Content-Type', 'application/json'); -+ res.end(JSON.stringify({ lastActiveTimestamp: data })); -+ } catch (error) { -+ serveError(req, res, 500, error.message) -+ } -+ } ++ * Handles API requests to retrieve the last activity timestamp. ++ */ ++ private async _handleIdle(req: http.IncomingMessage, res: http.ServerResponse): Promise { ++ try { ++ const tmpDirectory = '/tmp/' ++ const idleFilePath = join(tmpDirectory, '.sagemaker-last-active-timestamp'); ++ ++ // If idle shutdown file does not exist, this indicates the app UI may never been opened ++ // Create the initial metadata file ++ if (!existsSync(idleFilePath)) { ++ const timestamp = new Date().toISOString(); ++ writeFileSync(idleFilePath, timestamp); ++ } ++ ++ const data = await promises.readFile(idleFilePath, 'utf8'); ++ ++ res.statusCode = 200; ++ res.setHeader('Content-Type', 'application/json'); ++ res.end(JSON.stringify({ lastActiveTimestamp: data })); ++ } catch (error) { ++ serveError(req, res, 500, error.message) ++ } ++ } } - - /** diff --git a/patches/sagemaker-integration.diff b/patches/sagemaker-integration.diff index 9af2d5ded..dabdf69db 100644 --- a/patches/sagemaker-integration.diff +++ b/patches/sagemaker-integration.diff @@ -3,11 +3,11 @@ Index: sagemaker-code-editor/vscode/src/vs/workbench/browser/client.ts --- /dev/null +++ sagemaker-code-editor/vscode/src/vs/workbench/browser/client.ts @@ -0,0 +1,61 @@ -+import { Disposable } from 'vs/base/common/lifecycle'; -+import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -+import { MenuId, MenuRegistry } from "vs/platform/actions/common/actions"; -+import { localize } from "vs/nls"; -+import { ILogService } from "vs/platform/log/common/log"; ++import { Disposable } from '../../base/common/lifecycle.js'; ++import { CommandsRegistry } from '../../platform/commands/common/commands.js'; ++import { MenuId, MenuRegistry } from "../../platform/actions/common/actions.js"; ++import { localize } from "../../nls.js"; ++import { ILogService } from "../../platform/log/common/log.js"; + +export class SagemakerServerClient extends Disposable { + constructor ( @@ -69,11 +69,11 @@ Index: sagemaker-code-editor/vscode/src/vs/workbench/browser/web.main.ts =================================================================== --- sagemaker-code-editor.orig/vscode/src/vs/workbench/browser/web.main.ts +++ sagemaker-code-editor/vscode/src/vs/workbench/browser/web.main.ts -@@ -95,6 +95,7 @@ import { TunnelSource } from 'vs/workbench/services/re - import { TunnelSource } from 'vs/workbench/services/remote/common/tunnelModel'; - import { mainWindow } from 'vs/base/browser/window'; - import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -+import { SagemakerServerClient } from 'vs/workbench/browser/client'; +@@ -95,6 +95,7 @@ import { TunnelSource } from '../services/remote/common/tunnelModel.js'; + import { TunnelSource } from '../services/remote/common/tunnelModel.js'; + import { mainWindow } from '../../base/browser/window.js'; + import { INotificationService, Severity } from '../../platform/notification/common/notification.js'; ++import { SagemakerServerClient } from './client.js'; export class BrowserMain extends Disposable { @@ -106,8 +106,8 @@ Index: sagemaker-code-editor/vscode/product.json "builtInExtensions": [ - { - "name": "ms-vscode.js-debug-companion", -- "version": "1.1.2", -- "sha256": "e034b8b41beb4e97e02c70f7175bd88abe66048374c2bd629f54bb33354bc2aa", +- "version": "1.1.3", +- "sha256": "7380a890787452f14b2db7835dfa94de538caf358ebc263f9d46dd68ac52de93", - "repo": "https://github.com/microsoft/vscode-js-debug-companion", - "metadata": { - "id": "99cb0b7f-7354-4278-b8da-6cc79972169d", @@ -122,8 +122,8 @@ Index: sagemaker-code-editor/vscode/product.json - }, - { - "name": "ms-vscode.js-debug", -- "version": "1.90.0", -- "sha256": "1317dd7d1ac50641c1534a3e957ecbc94349f4fbd897acb916da11eea3208a66", +- "version": "1.100.1", +- "sha256": "8c2218df3422d45b95e96d9d28cdc4aa4426a2799aaaedd862d3f60ecab03844", - "repo": "https://github.com/microsoft/vscode-js-debug", - "metadata": { - "id": "25629058-ddac-4e17-abba-74678e126c5d", @@ -138,8 +138,8 @@ Index: sagemaker-code-editor/vscode/product.json - }, - { - "name": "ms-vscode.vscode-js-profile-table", -- "version": "1.0.9", -- "sha256": "3b62ee4276a2bbea3fe230f94b1d5edd915b05966090ea56f882e1e0ab53e1a6", +- "version": "1.0.10", +- "sha256": "7361748ddf9fd09d8a2ed1f2a2d7376a2cf9aae708692820b799708385c38e08", - "repo": "https://github.com/microsoft/vscode-js-profile-visualizer", - "metadata": { - "id": "7e52b41b-71ad-457b-ab7e-0620f1fc4feb", @@ -172,7 +172,7 @@ Index: sagemaker-code-editor/vscode/src/vs/platform/product/common/product.ts @@ -59,15 +59,17 @@ else { if (Object.keys(product).length === 0) { Object.assign(product, { - version: '1.90.0-dev', + version: '1.95.0-dev', - nameShort: 'Code - OSS Dev', - nameLong: 'Code - OSS Dev', + nameShort: 'CodeEditor', @@ -229,152 +229,6 @@ Index: sagemaker-code-editor/vscode/extensions/git/package.nls.json "config.timeline.showAuthor": "Controls whether to show the commit author in the Timeline view.", "config.timeline.showUncommitted": "Controls whether to show uncommitted changes in the Timeline view.", "config.timeline.date": "Controls which date to use for items in the Timeline view.", -@@ -301,7 +301,7 @@ - "message": "[Download Git for Windows](https://git-scm.com/download/win)\nAfter installing, please [reload](command:workbench.action.reloadWindow) (or [troubleshoot](command:git.showOutput)). Additional source control providers can be installed [from the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22).", - "comment": [ - "{Locked='](command:workbench.action.reloadWindow'}", -- "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", - "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" - ] - }, -@@ -309,7 +309,7 @@ - "message": "[Download Git for macOS](https://git-scm.com/download/mac)\nAfter installing, please [reload](command:workbench.action.reloadWindow) (or [troubleshoot](command:git.showOutput)). Additional source control providers can be installed [from the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22).", - "comment": [ - "{Locked='](command:workbench.action.reloadWindow'}", -- "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", - "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" - ] - }, -@@ -317,48 +317,48 @@ - "message": "Source control depends on Git being installed.\n[Download Git for Linux](https://git-scm.com/download/linux)\nAfter installing, please [reload](command:workbench.action.reloadWindow) (or [troubleshoot](command:git.showOutput)). Additional source control providers can be installed [from the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22).", - "comment": [ - "{Locked='](command:workbench.action.reloadWindow'}", -- "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", - "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" - ] - }, - "view.workbench.scm.missing": "Install Git, a popular source control system, to track code changes and collaborate with others. Learn more in our [Git guides](https://aka.ms/vscode-scm).", - "view.workbench.scm.disabled": { -- "message": "If you would like to use Git features, please enable Git in your [settings](command:workbench.action.openSettings?%5B%22git.enabled%22%5D).\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", -+ "message": "If you would like to use Git features, please enable Git in your [settings](command:workbench.action.openSettings?%5B%22git.enabled%22%5D).\nTo learn more about how to use Git and source control in Code-OSS [read our docs](https://aka.ms/vscode-scm).", - "comment": [ - "{Locked='](command:workbench.action.openSettings?%5B%22git.enabled%22%5D'}", -- "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", - "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" - ] - }, - "view.workbench.scm.empty": { -- "message": "In order to use Git features, you can open a folder containing a Git repository or clone from a URL.\n[Open Folder](command:vscode.openFolder)\n[Clone Repository](command:git.clone)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", -+ "message": "In order to use Git features, you can open a folder containing a Git repository or clone from a URL.\n[Open Folder](command:vscode.openFolder)\n[Clone Repository](command:git.clone)\nTo learn more about how to use Git and source control in Code-OSS [read our docs](https://aka.ms/vscode-scm).", - "comment": [ - "{Locked='](command:vscode.openFolder'}", -- "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", - "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" - ] - }, - "view.workbench.scm.folder": { -- "message": "The folder currently open doesn't have a Git repository. You can initialize a repository which will enable source control features powered by Git.\n[Initialize Repository](command:git.init?%5Btrue%5D)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", -+ "message": "The folder currently open doesn't have a Git repository. You can initialize a repository which will enable source control features powered by Git.\n[Initialize Repository](command:git.init?%5Btrue%5D)\nTo learn more about how to use Git and source control in Code-OSS [read our docs](https://aka.ms/vscode-scm).", - "comment": [ - "{Locked='](command:git.init?%5Btrue%5D'}", -- "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", - "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" - ] - }, - "view.workbench.scm.workspace": { -- "message": "The workspace currently open doesn't have any folders containing Git repositories. You can initialize a repository on a folder which will enable source control features powered by Git.\n[Initialize Repository](command:git.init)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", -+ "message": "The workspace currently open doesn't have any folders containing Git repositories. You can initialize a repository on a folder which will enable source control features powered by Git.\n[Initialize Repository](command:git.init)\nTo learn more about how to use Git and source control in Code-OSS [read our docs](https://aka.ms/vscode-scm).", - "comment": [ - "{Locked='](command:git.init'}", -- "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", - "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" - ] - }, - "view.workbench.scm.emptyWorkspace": { -- "message": "The workspace currently open doesn't have any folders containing Git repositories.\n[Add Folder to Workspace](command:workbench.action.addRootFolder)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", -+ "message": "The workspace currently open doesn't have any folders containing Git repositories.\n[Add Folder to Workspace](command:workbench.action.addRootFolder)\nTo learn more about how to use Git and source control in Code-OSS [read our docs](https://aka.ms/vscode-scm).", - "comment": [ - "{Locked='](command:workbench.action.addRootFolder'}", -- "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", - "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" - ] - }, -@@ -373,7 +373,7 @@ - "comment": [ - "{Locked='](command:git.openRepositoriesInParentFolders'}", - "{Locked='](command:workbench.action.openSettings?%5B%22git.openRepositoryInParentFolders%22%5D'}", -- "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", - "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" - ] - }, -@@ -382,7 +382,7 @@ - "comment": [ - "{Locked='](command:git.openRepositoriesInParentFolders'}", - "{Locked='](command:workbench.action.openSettings?%5B%22git.openRepositoryInParentFolders%22%5D'}", -- "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", - "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" - ] - }, -@@ -390,7 +390,7 @@ - "message": "The detected Git repository is potentially unsafe as the folder is owned by someone other than the current user.\n[Manage Unsafe Repositories](command:git.manageUnsafeRepositories)\nTo learn more about unsafe repositories [read our docs](https://aka.ms/vscode-git-unsafe-repository).", - "comment": [ - "{Locked='](command:git.manageUnsafeRepositories'}", -- "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", - "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" - ] - }, -@@ -398,23 +398,23 @@ - "message": "The detected Git repositories are potentially unsafe as the folders are owned by someone other than the current user.\n[Manage Unsafe Repositories](command:git.manageUnsafeRepositories)\nTo learn more about unsafe repositories [read our docs](https://aka.ms/vscode-git-unsafe-repository).", - "comment": [ - "{Locked='](command:git.manageUnsafeRepositories'}", -- "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", - "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" - ] - }, - "view.workbench.scm.closedRepository": { -- "message": "A Git repository was found that was previously closed.\n[Reopen Closed Repository](command:git.reopenClosedRepositories)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", -+ "message": "A Git repository was found that was previously closed.\n[Reopen Closed Repository](command:git.reopenClosedRepositories)\nTo learn more about how to use Git and source control in Code-OSS [read our docs](https://aka.ms/vscode-scm).", - "comment": [ - "{Locked='](command:git.reopenClosedRepositories'}", -- "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", - "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" - ] - }, - "view.workbench.scm.closedRepositories": { -- "message": "Git repositories were found that were previously closed.\n[Reopen Closed Repositories](command:git.reopenClosedRepositories)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", -+ "message": "Git repositories were found that were previously closed.\n[Reopen Closed Repositories](command:git.reopenClosedRepositories)\nTo learn more about how to use Git and source control in Code-OSS [read our docs](https://aka.ms/vscode-scm).", - "comment": [ - "{Locked='](command:git.reopenClosedRepositories'}", -- "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", - "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" - ] - }, -@@ -422,9 +422,9 @@ - "message": "You can clone a repository locally.\n[Clone Repository](command:git.clone 'Clone a repository once the Git extension has activated')", - "comment": [ - "{Locked='](command:git.clone'}", -- "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Code-OSS", - "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" - ] - }, -- "view.workbench.learnMore": "To learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm)." -+ "view.workbench.learnMore": "To learn more about how to use Git and source control in Code-OSS [read our docs](https://aka.ms/vscode-scm)." - } Index: sagemaker-code-editor/vscode/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts =================================================================== --- sagemaker-code-editor.orig/vscode/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts diff --git a/patches/sagemaker-open-notebook-extension.patch b/patches/sagemaker-open-notebook-extension.patch new file mode 100644 index 000000000..ad3dc5aac --- /dev/null +++ b/patches/sagemaker-open-notebook-extension.patch @@ -0,0 +1,317 @@ +Index: sagemaker-code-editor/vscode/build/gulpfile.extensions.js +=================================================================== +--- sagemaker-code-editor.orig/vscode/build/gulpfile.extensions.js ++++ sagemaker-code-editor/vscode/build/gulpfile.extensions.js +@@ -63,6 +63,7 @@ const compilations = [ + 'extensions/sagemaker-extension/tsconfig.json', + 'extensions/sagemaker-idle-extension/tsconfig.json', + 'extensions/sagemaker-terminal-crash-mitigation/tsconfig.json', ++ 'extensions/sagemaker-open-notebook-extension/tsconfig.json', + 'extensions/tunnel-forwarding/tsconfig.json', + 'extensions/typescript-language-features/test-workspace/tsconfig.json', + 'extensions/typescript-language-features/web/tsconfig.json', +Index: sagemaker-code-editor/vscode/build/npm/dirs.js +=================================================================== +--- sagemaker-code-editor.orig/vscode/build/npm/dirs.js ++++ sagemaker-code-editor/vscode/build/npm/dirs.js +@@ -42,6 +42,7 @@ const dirs = [ + 'extensions/sagemaker-extension', + 'extensions/sagemaker-idle-extension', + 'extensions/sagemaker-terminal-crash-mitigation', ++ 'extensions/sagemaker-open-notebook-extension', + 'extensions/search-result', + 'extensions/simple-browser', + 'extensions/tunnel-forwarding', +Index: sagemaker-code-editor/vscode/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts ++++ sagemaker-code-editor/vscode/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +@@ -262,6 +262,11 @@ Registry.as(Conf + description: localize('extensionsInQuickAccess', "When enabled, extensions can be searched for via Quick Access and report issues from there."), + default: true + }, ++ 'extensions.openNotebookData': { ++ type: 'object', ++ scope: ConfigurationScope.APPLICATION, ++ default: {}, ++ }, + [VerifyExtensionSignatureConfigKey]: { + type: 'boolean', + description: localize('extensions.verifySignature', "When enabled, extensions are verified to be signed before getting installed."), +Index: sagemaker-code-editor/vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts ++++ sagemaker-code-editor/vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +@@ -950,6 +950,17 @@ export class ExtensionsWorkbenchService + urlService.registerHandler(this); + + this.whenInitialized = this.initialize(); ++ ++ //Update workspace to open notebook ++ const urlParams = new URLSearchParams(window.location.search); ++ const notebookKey = urlParams.get('openNotebook'); ++ const clusterId = urlParams.get('clusterId'); ++ const region = urlParams.get('region'); ++ configurationService.updateValue('extensions.openNotebookData', { ++ notebookKey: notebookKey, ++ clusterId: clusterId, ++ region: region, ++ }); + } + + private async initialize(): Promise { +Index: sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/extension-browser.webpack.config.js +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/extension-browser.webpack.config.js +@@ -0,0 +1,17 @@ ++/*--------------------------------------------------------------------------------------------- ++ * Copyright Amazon.com Inc. or its affiliates. All rights reserved. ++ * Licensed under the MIT License. See License.txt in the project root for license information. ++ *--------------------------------------------------------------------------------------------*/ ++ ++//@ts-check ++ ++'use strict'; ++ ++const withBrowserDefaults = require('../shared.webpack.config').browser; ++ ++module.exports = withBrowserDefaults({ ++ context: __dirname, ++ entry: { ++ extension: './src/extension.ts' ++ }, ++}); +Index: sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/extension.webpack.config.js +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/extension.webpack.config.js +@@ -0,0 +1,20 @@ ++/*--------------------------------------------------------------------------------------------- ++ * Copyright Amazon.com Inc. or its affiliates. All rights reserved. ++ * Licensed under the MIT License. See License.txt in the project root for license information. ++ *--------------------------------------------------------------------------------------------*/ ++ ++//@ts-check ++ ++'use strict'; ++ ++const withDefaults = require('../shared.webpack.config'); ++ ++module.exports = withDefaults({ ++ context: __dirname, ++ resolve: { ++ mainFields: ['module', 'main'] ++ }, ++ entry: { ++ extension: './src/extension.ts', ++ } ++}); +Index: sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/package.json +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/package.json +@@ -0,0 +1,44 @@ ++{ ++ "name": "sagemaker-open-notebook-extension", ++ "displayName": "Sagemaker open notebook Extension", ++ "description": "To download and open sample notebook when open code editor", ++ "extensionKind": [ ++ "workspace" ++ ], ++ "version": "1.0.0", ++ "publisher": "sagemaker", ++ "license": "MIT", ++ "engines": { ++ "vscode": "^1.70.0" ++ }, ++ "main": "./out/extension", ++ "categories": [ ++ "Other" ++ ], ++ "activationEvents": [ ++ "*" ++ ], ++ "capabilities": { ++ "virtualWorkspaces": true, ++ "untrustedWorkspaces": { ++ "supported": true ++ } ++ }, ++ "contributes": { ++ "configuration": { ++ "type": "object", ++ "title": "Sagemaker Open Notebook Extension", ++ "properties": {} ++ }, ++ "commands": [ ++ ] ++ }, ++ "scripts": { ++ "compile": "gulp compile-extension:sagemaker-open-notebook-extension", ++ "watch": "npm run build-preview && gulp watch-extension:sagemaker-open-notebook-extension", ++ "vscode:prepublish": "npm run build-ext", ++ "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:sagemaker-open-notebook-extension ./tsconfig.json" ++ }, ++ "dependencies": {}, ++ "repository": {} ++} +Index: sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/src/extension.ts +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/src/extension.ts +@@ -0,0 +1,100 @@ ++ ++import * as vscode from 'vscode'; ++import * as https from 'https'; ++import * as fs from 'fs'; ++import * as path from 'path'; ++import * as os from 'os'; ++import * as console from 'console'; ++ ++export function activate() { ++ const config = vscode.workspace.getConfiguration('extensions.openNotebookData'); ++ const notebookKey = config.get('notebookKey') as string; ++ const clusterId = config.get('clusterId') as string; ++ const region = config.get('region') as string; ++ if(notebookKey){ ++ loadAndDisplayNotebook(notebookKey, clusterId, region); ++ } ++ ++} ++ ++function isValidRegion(region: string): boolean { ++ // This regex allows for characters, numbers, and hyphens ++ const regionRegex = /^[a-zA-Z0-9-]+$/; ++ return regionRegex.test(region); ++} ++ ++async function loadAndDisplayNotebook(fileKey: string, clusterId: string, region: string) { ++ if (!isValidRegion(region)) { ++ vscode.window.showErrorMessage('Invalid region format. Region should only contain characters, numbers, and hyphens.'); ++ return; ++ } ++ ++ const bucketName = `jumpstart-cache-prod-${region}`; ++ const url = `https://${bucketName}.s3.${region}.amazonaws.com/${fileKey}`; ++ try { ++ let content = await downloadFile(url); ++ content = processNotebookContent(content, clusterId, region); ++ const tempDir = os.tmpdir(); ++ const tempFilePath = path.join(tempDir, 'downloaded-notebook.ipynb'); ++ fs.writeFileSync(tempFilePath, content); ++ const uri = vscode.Uri.file(tempFilePath); ++ await openNotebookDocument(uri); ++ } catch (error) { ++ vscode.window.showErrorMessage('Error downloading or opening notebook: ' + error.message); ++ } ++} ++ ++function processNotebookContent(content: string, clusterId: string, region: string): string { ++ const notebook = JSON.parse(content); ++ notebook.cells = notebook.cells.map((cell: any) => { ++ if (cell.metadata && ++ cell.metadata.jumpStartAlterations && ++ cell.metadata.jumpStartAlterations.includes('clusterId')) { ++ cell.source = [ ++ "%%bash\n", ++ `aws ssm start-session --target sagemaker-cluster:${clusterId} --region ${region}` ++ ]; ++ cell.cell_type = "code"; ++ } ++ ++ if (cell.metadata && ++ cell.metadata.jumpStartAlterations && ++ cell.metadata.jumpStartAlterations.includes('clusterName')) { ++ cell.source = [ ++ `!hyperpod connect-cluster --cluster-name ${clusterId}` ++ ] ++ cell.cell_type = "code"; ++ } ++ return cell; ++ }); ++ return JSON.stringify(notebook, null, 2); ++} ++ ++async function openNotebookDocument(uri: vscode.Uri) { ++ try { ++ // Open the notebook document ++ const document = await vscode.workspace.openNotebookDocument(uri); ++ // Show the notebook document in a notebook editor ++ await vscode.window.showNotebookDocument(document); ++ } catch (error) { ++ console.error('Failed to open notebook:', error); ++ vscode.window.showErrorMessage('Failed to open notebook: ' + error.message); ++ } ++} ++ ++function downloadFile(url: string): Promise { ++ return new Promise((resolve, reject) => { ++ https.get(url, (response) => { ++ let data = ''; ++ response.on('data', (chunk) => { ++ data += chunk; ++ }); ++ response.on('end', () => { ++ resolve(data); ++ }); ++ }).on('error', (error) => { ++ reject(error); ++ }); ++ }); ++} ++export function deactivate() {} +Index: sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/tsconfig.json +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/tsconfig.json +@@ -0,0 +1,10 @@ ++{ ++ "extends": "../tsconfig.base.json", ++ "compilerOptions": { ++ "outDir": "./out" ++ }, ++ "include": [ ++ "../sagemaker-open-notebook-extension/src/**/*", ++ "../../src/vscode-dts/vscode.d.ts" ++ ] ++} +Index: sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/.vscodeignore +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/.vscodeignore +@@ -0,0 +1,11 @@ ++.vscode/** ++.vscode-test/** ++out/test/** ++out/** ++test/** ++src/** ++tsconfig.json ++out/test/** ++out/** ++cgmanifest.json ++preview-src/** +Index: sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/README.md +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/README.md +@@ -0,0 +1,18 @@ ++# Code Editor Open Notebook Extension ++ ++The Open Notebook extension enables users to download, transform, and display sample notebooks from a public Amazon S3 bucket owned by the SageMaker team. This extension streamlines the process of accessing and working with SageMaker sample notebooks directly within Code Editor. ++ ++## Features ++ ++- Download sample notebooks from a specified S3 bucket ++- Transform notebooks for compatibility with VSCode ++- Display notebooks within the Code Editor environment ++- Utilize URL parameters to open specific notebooks ++ ++## Usage ++ ++The extension uses parameters from the URL to open the desired notebook. The required parameters are: ++- Notebook key: The identifier for the specific notebook in the S3 bucket ++- Cluster ID: The ID of the SageMaker cluster ++- Region: The AWS region where the S3 bucket is located ++ diff --git a/patches/sagemaker-ui-dark-theme.patch b/patches/sagemaker-ui-dark-theme.patch new file mode 100644 index 000000000..033d7ed53 --- /dev/null +++ b/patches/sagemaker-ui-dark-theme.patch @@ -0,0 +1,397 @@ +Index: sagemaker-code-editor/vscode/extensions/sagemaker-ui-dark-theme/.vscodeignore +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-ui-dark-theme/.vscodeignore +@@ -0,0 +1,11 @@ ++.vscode/** ++.vscode-test/** ++out/test/** ++out/** ++test/** ++src/** ++tsconfig.json ++out/test/** ++out/** ++cgmanifest.json ++preview-src/** +Index: sagemaker-code-editor/vscode/extensions/sagemaker-ui-dark-theme/README.md +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-ui-dark-theme/README.md +@@ -0,0 +1 @@ ++# SageMaker UI Dark Theme +Index: sagemaker-code-editor/vscode/extensions/sagemaker-ui-dark-theme/extension-browser.webpack.config.js +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-ui-dark-theme/extension-browser.webpack.config.js +@@ -0,0 +1,17 @@ ++/*--------------------------------------------------------------------------------------------- ++ * Copyright Amazon.com Inc. or its affiliates. All rights reserved. ++ * Licensed under the MIT License. See License.txt in the project root for license information. ++ *--------------------------------------------------------------------------------------------*/ ++ ++//@ts-check ++ ++'use strict'; ++ ++const withBrowserDefaults = require('../shared.webpack.config').browser; ++ ++module.exports = withBrowserDefaults({ ++ context: __dirname, ++ entry: { ++ extension: './src/extension.ts' ++ }, ++}); +Index: sagemaker-code-editor/vscode/extensions/sagemaker-ui-dark-theme/extension.webpack.config.js +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-ui-dark-theme/extension.webpack.config.js +@@ -0,0 +1,20 @@ ++/*--------------------------------------------------------------------------------------------- ++ * Copyright Amazon.com Inc. or its affiliates. All rights reserved. ++ * Licensed under the MIT License. See License.txt in the project root for license information. ++ *--------------------------------------------------------------------------------------------*/ ++ ++//@ts-check ++ ++'use strict'; ++ ++const withDefaults = require('../shared.webpack.config'); ++ ++module.exports = withDefaults({ ++ context: __dirname, ++ resolve: { ++ mainFields: ['module', 'main'] ++ }, ++ entry: { ++ extension: './src/extension.ts', ++ } ++}); +Index: sagemaker-code-editor/vscode/extensions/sagemaker-ui-dark-theme/src/extension.ts +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-ui-dark-theme/src/extension.ts +@@ -0,0 +1,51 @@ ++import * as vscode from 'vscode'; ++ ++const SERVICE_NAME_ENV_KEY = 'SERVICE_NAME'; ++const SERVICE_NAME_ENV_VALUE = 'SageMakerUnifiedStudio'; ++const DEFAULT_THEME = 'Default Dark Modern'; ++ ++let outputChannel: vscode.OutputChannel; ++ ++export function activate() { ++ // Check if in SageMaker Unified Studio ++ const envValue = process.env[SERVICE_NAME_ENV_KEY]; ++ if (!envValue || envValue !== SERVICE_NAME_ENV_VALUE) { ++ return; ++ } ++ ++ const config = vscode.workspace.getConfiguration(); ++ const themeConfig = config.inspect('workbench.colorTheme'); ++ outputChannel = vscode.window.createOutputChannel('SageMaker UI Dark Theme'); ++ ++ outputChannel.appendLine(`Current theme configuration: ${JSON.stringify(themeConfig, null, 2)}`); ++ ++ // Check if theme is only set at default level ++ if (themeConfig?.globalValue === undefined && ++ themeConfig?.workspaceValue === undefined && ++ themeConfig?.workspaceFolderValue === undefined) { ++ ++ outputChannel.appendLine('Theme only set at default level, applying theme update'); ++ ++ // Update the configuration ++ Promise.resolve( ++ config.update('workbench.colorTheme', DEFAULT_THEME, vscode.ConfigurationTarget.Global) ++ .then(() => { ++ outputChannel.appendLine(`Theme configuration updated to ${DEFAULT_THEME}`); ++ // Reload to apply theme ++ return vscode.commands.executeCommand('workbench.action.reloadWindow'); ++ }) ++ .then(() => outputChannel.appendLine('Theme applied successfully')) ++ ) ++ .catch((error) => { ++ outputChannel.appendLine(`Failed to apply theme: ${error}`); ++ }); ++ } else { ++ outputChannel.appendLine('Theme already configured in user or workspace settings, not overriding'); ++ } ++} ++ ++export function deactivate() { ++ if (outputChannel) { ++ outputChannel.dispose(); ++ } ++} +Index: sagemaker-code-editor/vscode/extensions/sagemaker-ui-dark-theme/tsconfig.json +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-ui-dark-theme/tsconfig.json +@@ -0,0 +1,10 @@ ++{ ++ "extends": "../tsconfig.base.json", ++ "compilerOptions": { ++ "outDir": "./out" ++ }, ++ "include": [ ++ "src/**/*", ++ "../../src/vscode-dts/vscode.d.ts" ++ ] ++} +Index: sagemaker-code-editor/vscode/build/gulpfile.extensions.js +=================================================================== +--- sagemaker-code-editor.orig/vscode/build/gulpfile.extensions.js ++++ sagemaker-code-editor/vscode/build/gulpfile.extensions.js +@@ -64,6 +64,7 @@ const compilations = [ + 'extensions/sagemaker-idle-extension/tsconfig.json', + 'extensions/sagemaker-terminal-crash-mitigation/tsconfig.json', + 'extensions/sagemaker-open-notebook-extension/tsconfig.json', ++ 'extensions/sagemaker-ui-dark-theme/tsconfig.json', + 'extensions/tunnel-forwarding/tsconfig.json', + 'extensions/typescript-language-features/test-workspace/tsconfig.json', + 'extensions/typescript-language-features/web/tsconfig.json', +Index: sagemaker-code-editor/vscode/build/npm/dirs.js +=================================================================== +--- sagemaker-code-editor.orig/vscode/build/npm/dirs.js ++++ sagemaker-code-editor/vscode/build/npm/dirs.js +@@ -42,6 +42,7 @@ const dirs = [ + 'extensions/sagemaker-idle-extension', + 'extensions/sagemaker-terminal-crash-mitigation', + 'extensions/sagemaker-open-notebook-extension', ++ 'extensions/sagemaker-ui-dark-theme', + 'extensions/search-result', + 'extensions/simple-browser', + 'extensions/tunnel-forwarding', +Index: sagemaker-code-editor/vscode/extensions/sagemaker-ui-dark-theme/package.json +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-ui-dark-theme/package.json +@@ -0,0 +1,46 @@ ++{ ++ "name": "sagemaker-ui-dark-theme", ++ "displayName": "SageMaker UI Dark Theme", ++ "description": "SageMaker UI Dark Theme", ++ "extensionKind": [ ++ "workspace" ++ ], ++ "version": "1.0.0", ++ "publisher": "sagemaker", ++ "license": "MIT", ++ "engines": { ++ "vscode": "^1.70.0" ++ }, ++ "main": "./out/extension", ++ "categories": [ ++ "Other" ++ ], ++ "activationEvents": [ ++ "onStartupFinished" ++ ], ++ "capabilities": { ++ "virtualWorkspaces": true, ++ "untrustedWorkspaces": { ++ "supported": true ++ } ++ }, ++ "contributes": { ++ "configuration": { ++ "type": "object", ++ "title": "SageMaker UI Dark Theme", ++ "properties": {} ++ }, ++ "commands": [ ++ ] ++ }, ++ "scripts": { ++ "compile": "gulp compile-extension:sagemaker-ui-dark-theme", ++ "watch": "npm run build-preview && gulp watch-extension:sagemaker-ui-dark-theme", ++ "vscode:prepublish": "npm run build-ext", ++ "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:sagemaker-ui-dark-theme ./tsconfig.json" ++ }, ++ "dependencies": { ++ }, ++ "repository": { ++ } ++} +Index: sagemaker-code-editor/vscode/.vscode-test.js +=================================================================== +--- sagemaker-code-editor.orig/vscode/.vscode-test.js ++++ sagemaker-code-editor/vscode/.vscode-test.js +@@ -63,6 +63,11 @@ const extensions = [ + mocha: { timeout: 60_000 } + }, + { ++ label: 'sagemaker-ui-dark-theme', ++ workspaceFolder: `extensions/sagemaker-ui-dark-theme/test-workspace`, ++ mocha: { timeout: 60_000 } ++ }, ++ { + label: 'microsoft-authentication', + mocha: { timeout: 60_000 } + }, +Index: sagemaker-code-editor/vscode/extensions/sagemaker-ui-dark-theme/src/test/extension.test.ts +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-ui-dark-theme/src/test/extension.test.ts +@@ -0,0 +1,123 @@ ++import * as assert from 'assert'; ++import * as vscode from 'vscode'; ++ ++const DEFAULT_DARK_MODERN = 'Default Dark Modern'; ++const DEFAULT_LIGHT_MODERN = 'Default Light Modern'; ++ ++async function waitForThemeChange(expectedTheme: string | undefined, timeoutMs: number): Promise { ++ const startTime = Date.now(); ++ ++ while (Date.now() - startTime < timeoutMs) { ++ const currentTheme = vscode.workspace.getConfiguration('workbench').inspect('colorTheme'); ++ ++ if (currentTheme?.globalValue === expectedTheme) { ++ return; ++ } ++ await new Promise(resolve => setTimeout(resolve, 100)); ++ } ++ throw new Error(`Theme did not change to ${expectedTheme} at the global level within ${timeoutMs}ms`); ++} ++ ++suite('SageMaker UI Dark Theme Extension Tests - In SageMaker Unified Studio Environment', () => { ++ // Store original ENV variable value ++ const originalEnv = process.env.SERVICE_NAME; ++ ++ suiteSetup(() => { ++ // Clear the theme configurations ++ vscode.workspace.getConfiguration('workbench').update('colorTheme', undefined, vscode.ConfigurationTarget.Global); ++ vscode.workspace.getConfiguration('workbench').update('colorTheme', undefined, vscode.ConfigurationTarget.Workspace); ++ ++ // Set ENV variable value for SageMaker Unified Studio environment ++ process.env.SERVICE_NAME = 'SageMakerUnifiedStudio'; ++ }); ++ ++ suiteTeardown(() => { ++ // Clear the theme configurations ++ vscode.workspace.getConfiguration('workbench').update('colorTheme', undefined, vscode.ConfigurationTarget.Global); ++ ++ // Restore ENV variable value to original ++ originalEnv ? (process.env.SERVICE_NAME = originalEnv) : delete process.env.SERVICE_NAME; ++ }); ++ ++ test('Theme is set when global and workspace theme configurations are unset', async () => { ++ // Poll for theme update ++ await waitForThemeChange(DEFAULT_DARK_MODERN, 10000); ++ ++ const config = vscode.workspace.getConfiguration(); ++ const theme = config.inspect('workbench.colorTheme'); ++ ++ assert.strictEqual(theme?.globalValue, DEFAULT_DARK_MODERN, `Global theme should be set to ${DEFAULT_DARK_MODERN}`); ++ }); ++}); ++ ++suite('SageMaker UI Dark Theme Extension Tests - In SageMaker Unified Studio Environment', () => { ++ // Store original ENV variable value ++ const originalEnv = process.env.SERVICE_NAME; ++ ++ suiteSetup(() => { ++ // Set the global theme configuration to Default Light Modern ++ vscode.workspace.getConfiguration('workbench').update('colorTheme', DEFAULT_LIGHT_MODERN, vscode.ConfigurationTarget.Global); ++ vscode.workspace.getConfiguration('workbench').update('colorTheme', undefined, vscode.ConfigurationTarget.Workspace); ++ ++ // Set ENV variable value for SageMaker Unified Studio environment ++ process.env.SERVICE_NAME = 'SageMakerUnifiedStudio'; ++ }); ++ ++ suiteTeardown(() => { ++ // Clear the theme configurations ++ vscode.workspace.getConfiguration('workbench').update('colorTheme', undefined, vscode.ConfigurationTarget.Global); ++ ++ // Restore ENV variable value to original ++ originalEnv ? (process.env.SERVICE_NAME = originalEnv) : delete process.env.SERVICE_NAME; ++ }); ++ ++ test('Theme is not set when global theme configuration is set', async () => { ++ // Poll for theme update ++ await waitForThemeChange(DEFAULT_LIGHT_MODERN, 10000); ++ ++ // Poll for Default Dark Modern theme update (expected to fail) ++ try { ++ await waitForThemeChange(DEFAULT_DARK_MODERN, 10000); ++ assert.fail(`Global theme should be kept as ${DEFAULT_LIGHT_MODERN}`); ++ } catch (error) { ++ // Expected behavior: Theme should not be set ++ } ++ ++ const config = vscode.workspace.getConfiguration(); ++ const theme = config.inspect('workbench.colorTheme'); ++ ++ assert.strictEqual(theme?.globalValue, DEFAULT_LIGHT_MODERN, `Global theme should be kept as ${DEFAULT_LIGHT_MODERN}`); ++ }); ++}); ++ ++suite('SageMaker UI Dark Theme Extension Tests - In SageMaker AI Environment', () => { ++ // Store original ENV variable value ++ const originalEnv = process.env.SERVICE_NAME; ++ ++ suiteSetup(() => { ++ // Clear the global theme configuration ++ vscode.workspace.getConfiguration('workbench').update('colorTheme', undefined, vscode.ConfigurationTarget.Global); ++ vscode.workspace.getConfiguration('workbench').update('colorTheme', undefined, vscode.ConfigurationTarget.Workspace); ++ ++ // Ensure ENV variable value for SageMaker Unified Studio environment is NOT set ++ delete process.env.SERVICE_NAME; ++ }); ++ ++ suiteTeardown(() => { ++ // Clear the global theme configuration ++ vscode.workspace.getConfiguration('workbench').update('colorTheme', undefined, vscode.ConfigurationTarget.Global); ++ ++ // Restore ENV variable value to original ++ originalEnv ? (process.env.SERVICE_NAME = originalEnv) : delete process.env.SERVICE_NAME; ++ }); ++ ++ test('Theme is not set', async () => { ++ // Poll for theme update ++ await waitForThemeChange(undefined, 10000); ++ ++ const config = vscode.workspace.getConfiguration(); ++ const theme = config.inspect('workbench.colorTheme'); ++ ++ assert.strictEqual(theme?.globalValue, undefined, 'Global theme should not be set'); ++ }); ++}); +Index: sagemaker-code-editor/vscode/extensions/sagemaker-ui-dark-theme/src/test/index.ts +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-ui-dark-theme/src/test/index.ts +@@ -0,0 +1,33 @@ ++import * as path from 'path'; ++import * as testRunner from '../../../../test/integration/electron/testrunner'; ++ ++const options: import('mocha').MochaOptions = { ++ ui: 'tdd', ++ color: true, ++ timeout: 60000 ++}; ++ ++// Set the suite name ++let suite = ''; ++if (process.env.VSCODE_BROWSER) { ++ suite = `${process.env.VSCODE_BROWSER} Browser Integration SageMaker UI Dark Theme Tests`; ++} else if (process.env.REMOTE_VSCODE) { ++ suite = 'Remote Integration SageMaker UI Dark Theme Tests'; ++} else { ++ suite = 'Integration SageMaker UI Dark Theme Tests'; ++} ++ ++if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) { ++ options.reporter = 'mocha-multi-reporters'; ++ options.reporterOptions = { ++ reporterEnabled: 'spec, mocha-junit-reporter', ++ mochaJunitReporterReporterOptions: { ++ testsuitesTitle: `${suite} ${process.platform}`, ++ mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${process.arch}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`) ++ } ++ }; ++} ++ ++testRunner.configure(options); ++ ++export = testRunner; diff --git a/patches/sagemaker-ui-post-startup.patch b/patches/sagemaker-ui-post-startup.patch new file mode 100644 index 000000000..1d7e34933 --- /dev/null +++ b/patches/sagemaker-ui-post-startup.patch @@ -0,0 +1,82 @@ +Index: sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/server/node/webClientServer.ts ++++ sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts +@@ -6,6 +6,8 @@ + + import { createReadStream, promises, existsSync, writeFileSync } from 'fs'; + import * as http from 'http'; ++import { spawn } from 'child_process'; ++import * as fs from 'fs'; + import * as url from 'url'; + import * as cookie from 'cookie'; + import * as crypto from 'crypto'; +@@ -30,6 +32,10 @@ const textMimeType: { [ext: string]: st + '.svg': 'image/svg+xml', + }; + ++const enum ServiceName { ++ SAGEMAKER_UNIFIED_STUDIO = 'SageMakerUnifiedStudio', ++} ++ + /** + * Return an error to the client. + */ +@@ -93,6 +99,7 @@ const STATIC_PATH = `/static`; + const CALLBACK_PATH = `/callback`; + const WEB_EXTENSION_PATH = `/web-extension-resource`; + const IDLE_EXTENSION_PATH = `/api/idle`; ++const POST_STARTUP_SCRIPT_PATH = `/api/poststartup`; + + export class WebClientServer { + +@@ -134,6 +141,9 @@ export class WebClientServer { + if (pathname === IDLE_EXTENSION_PATH) { + return this._handleIdle(req, res); + } ++ if (pathname === POST_STARTUP_SCRIPT_PATH) { ++ return this._handlePostStartupScriptInvocation(req, res); ++ } + if (pathname.startsWith(WEB_EXTENSION_PATH) && pathname.charCodeAt(WEB_EXTENSION_PATH.length) === CharCode.Slash) { + // extension resource support + return this._handleWebExtensionResource(req, res, pathname.substring(WEB_EXTENSION_PATH.length)); +@@ -507,4 +517,39 @@ export class WebClientServer { + serveError(req, res, 500, error.message) + } + } ++ ++ /** ++ * Handles API requests to run the post-startup script in SMD. ++ */ ++ private async _handlePostStartupScriptInvocation(req: http.IncomingMessage, res: http.ServerResponse): Promise { ++ const postStartupScriptPath = '/etc/sagemaker-ui/sagemaker_ui_post_startup.sh' ++ const logPath = '/var/log/apps/post_startup_default.log'; ++ const logStream = fs.createWriteStream(logPath, { flags: 'a' }); ++ ++ // Only trigger post-startup script invocation for SageMakerUnifiedStudio app. ++ if (process.env['SERVICE_NAME'] != ServiceName.SAGEMAKER_UNIFIED_STUDIO) { ++ return serveError(req, res, 403, 'Forbidden'); ++ } else { ++ //If postStartupScriptFile doesn't exist, it will throw FileNotFoundError (404) ++ //If exists, it will start the execution and add the execution logs in logFile. ++ try { ++ if (fs.existsSync(postStartupScriptPath)) { ++ // Adding 0o755 to make script file executable ++ fs.chmodSync(postStartupScriptPath, 0o755); ++ ++ const subprocess = spawn('bash', [`${postStartupScriptPath}`], { cwd: '/' }); ++ subprocess.stdout.pipe(logStream); ++ subprocess.stderr.pipe(logStream); ++ ++ res.statusCode = 200; ++ res.setHeader('Content-Type', 'application/json'); ++ res.end(JSON.stringify({ 'success': 'true' })); ++ } else { ++ serveError(req, res, 500, 'Poststartup script file not found at ' + postStartupScriptPath); ++ } ++ } catch (error) { ++ serveError(req, res, 500, error.message); ++ } ++ } ++ } + } \ No newline at end of file diff --git a/patches/series b/patches/series index 9091c1335..dd773593c 100644 --- a/patches/series +++ b/patches/series @@ -1,10 +1,20 @@ sagemaker-extension.diff disable-online-services.diff disable-telemetry.diff -base-path.diff +update-csp.diff webview.diff local-storage.diff sagemaker-integration.diff license.diff +base-path-compatibility.diff sagemaker-idle-extension.patch -terminal-crash-mitigation.patch \ No newline at end of file +terminal-crash-mitigation.patch +sagemaker-open-notebook-extension.patch +sagemaker-ui-dark-theme.patch +sagemaker-ui-post-startup.patch +sagemaker-extension-smus-support.patch +post-startup-notifications.patch +sagemaker-extensions-sync.patch +custom-extensions-marketplace.diff +signature-verification.diff +display-language.patch \ No newline at end of file diff --git a/patches/signature-verification.diff b/patches/signature-verification.diff new file mode 100644 index 000000000..2ac20e9ec --- /dev/null +++ b/patches/signature-verification.diff @@ -0,0 +1,34 @@ +Disable signature verification. + +Extension signature verification is now mandatory for all platforms and needs to be disabled. + +Index: sagemaker-code-editor/vscode/src/vs/platform/extensionManagement/node/extensionManagementService.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/platform/extensionManagement/node/extensionManagementService.ts ++++ sagemaker-code-editor/vscode/src/vs/platform/extensionManagement/node/extensionManagementService.ts +@@ -34,6 +34,7 @@ import { + ExtensionSignatureVerificationCode, + computeSize, + IAllowedExtensionsService, ++ // @ts-expect-error no-unused-variable + VerifyExtensionSignatureConfigKey, + shouldRequireRepositorySignatureFor, + } from '../common/extensionManagement.js'; +@@ -87,6 +88,7 @@ export class ExtensionManagementService + @IDownloadService private downloadService: IDownloadService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IFileService private readonly fileService: IFileService, ++ // @ts-expect-error no-unused-variable + @IConfigurationService private readonly configurationService: IConfigurationService, + @IExtensionGalleryManifestService protected readonly extensionGalleryManifestService: IExtensionGalleryManifestService, + @IProductService productService: IProductService, +@@ -339,8 +341,7 @@ export class ExtensionManagementService + + private async downloadExtension(extension: IGalleryExtension, operation: InstallOperation, verifySignature: boolean, clientTargetPlatform?: TargetPlatform): Promise<{ readonly location: URI; readonly verificationStatus: ExtensionSignatureVerificationCode | undefined }> { + if (verifySignature) { +- const value = this.configurationService.getValue(VerifyExtensionSignatureConfigKey); +- verifySignature = isBoolean(value) ? value : true; ++ verifySignature = false; + } + const { location, verificationStatus } = await this.extensionsDownloader.download(extension, operation, verifySignature, clientTargetPlatform); + const shouldRequireSignature = shouldRequireRepositorySignatureFor(extension.private, await this.extensionGalleryManifestService.getExtensionGalleryManifest()); \ No newline at end of file diff --git a/patches/tar-fs-upgrade.patch b/patches/tar-fs-upgrade.patch new file mode 100644 index 000000000..c4c6c706a --- /dev/null +++ b/patches/tar-fs-upgrade.patch @@ -0,0 +1,34 @@ +Index: sagemaker-code-editor/vscode/build/yarn.lock +=================================================================== +--- sagemaker-code-editor.orig/vscode/build/yarn.lock ++++ sagemaker-code-editor/vscode/build/yarn.lock +@@ -2567,9 +2567,9 @@ supports-color@^7.1.0: + has-flag "^4.0.0" + + tar-fs@^2.0.0: +- version "2.1.1" +- resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" +- integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== ++ version "2.1.3" ++ resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.3.tgz#fb3b8843a26b6f13a08e606f7922875eb1fbbf92" ++ integrity sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" +Index: sagemaker-code-editor/vscode/remote/yarn.lock +=================================================================== +--- sagemaker-code-editor.orig/vscode/remote/yarn.lock ++++ sagemaker-code-editor/vscode/remote/yarn.lock +@@ -587,9 +587,9 @@ strip-json-comments@~2.0.1: + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + + tar-fs@^2.0.0: +- version "2.1.1" +- resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" +- integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== ++ version "2.1.3" ++ resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.3.tgz#fb3b8843a26b6f13a08e606f7922875eb1fbbf92" ++ integrity sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" \ No newline at end of file diff --git a/patches/terminal-crash-mitigation.patch b/patches/terminal-crash-mitigation.patch index 1e34b7d83..44a95e9ed 100644 --- a/patches/terminal-crash-mitigation.patch +++ b/patches/terminal-crash-mitigation.patch @@ -241,15 +241,6 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-terminal-crash-mitigati + "../../src/vscode-dts/vscode.d.ts" + ] +} -Index: sagemaker-code-editor/vscode/extensions/sagemaker-terminal-crash-mitigation/yarn.lock -=================================================================== ---- /dev/null -+++ sagemaker-code-editor/vscode/extensions/sagemaker-terminal-crash-mitigation/yarn.lock -@@ -0,0 +1,4 @@ -+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -+# yarn lockfile v1 -+ -+ Index: sagemaker-code-editor/vscode/build/npm/dirs.js =================================================================== --- sagemaker-code-editor.orig/vscode/build/npm/dirs.js diff --git a/patches/update-csp.diff b/patches/update-csp.diff new file mode 100644 index 000000000..5f49876bb --- /dev/null +++ b/patches/update-csp.diff @@ -0,0 +1,13 @@ +Index: sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/server/node/webClientServer.ts ++++ sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts +@@ -375,7 +375,7 @@ export class WebClientServer { + `frame-src 'self' https://*.vscode-cdn.net data:;`, + 'worker-src \'self\' data: blob:;', + 'style-src \'self\' \'unsafe-inline\';', +- 'connect-src \'self\' ws: wss: https:;', ++ 'connect-src \'self\' ws: wss: https://main.vscode-cdn.net http://localhost:* https://localhost:* https://login.microsoftonline.com/ https://update.code.visualstudio.com https://*.vscode-unpkg.net/ https://default.exp-tas.com/vscode/ab https://vscode-sync.trafficmanager.net https://vscode-sync-insiders.trafficmanager.net https://*.gallerycdn.vsassets.io https://marketplace.visualstudio.com https://az764295.vo.msecnd.net https://code.visualstudio.com https://*.gallery.vsassets.io https://*.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com https://*.servicebus.windows.net/ https://vscode.blob.core.windows.net https://vscode.search.windows.net https://vsmarketplacebadges.dev https://vscode.download.prss.microsoft.com https://download.visualstudio.microsoft.com https://*.vscode-unpkg.net https://open-vsx.org;', + 'font-src \'self\' blob:;', + 'manifest-src \'self\';' + ].join(' '); diff --git a/patches/webview.diff b/patches/webview.diff index adb72d452..66d26f7e4 100644 --- a/patches/webview.diff +++ b/patches/webview.diff @@ -60,8 +60,8 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts @@ -323,6 +323,7 @@ export class WebClientServer { const workbenchWebConfiguration = { remoteAuthority, - serverBasePath: this._basePath, -+ webviewEndpoint: vscodeBase + this._staticRoute + '/out/vs/workbench/contrib/webview/browser/pre', + serverBasePath: basePath, ++ webviewEndpoint: staticRoute + '/out/vs/workbench/contrib/webview/browser/pre', _wrapWebWorkerExtHostInIframe, developmentOptions: { enableSmokeTestDriver: this._environmentService.args['enable-smoke-test-driver'] ? true : undefined, logLevel: this._logService.getLevel() }, settingsSyncOptions: !this._environmentService.isBuilt && this._environmentService.args['enable-sync'] ? { enabled: true } : undefined, @@ -73,8 +73,8 @@ Index: sagemaker-code-editor/vscode/src/vs/workbench/contrib/webview/browser/pre -+ content="default-src 'none'; script-src 'sha256-R3BsSkqy7qFbvWSmwr7WqT1eg6Sq4zSe0uIlrUQ4EKE=' 'self'; frame-src 'self'; style-src 'unsafe-inline';"> +- content="default-src 'none'; script-src 'sha256-gEAyFzmkyqMoTTnN+3KReFUYoHsK4RAJEb+6eiul+UY=' 'self'; frame-src 'self'; style-src 'unsafe-inline';"> ++ content="default-src 'none'; script-src 'sha256-ap/AtocvSWp0rrxaO19DJy/nOpazT6M5Cv9utUWe7MA=' 'self'; frame-src 'self'; style-src 'unsafe-inline';"> -@@ -23,6 +23,13 @@ +@@ -25,6 +25,13 @@ // validation not requested return start(); } diff --git a/scripts/Dockerfile.build b/scripts/Dockerfile.build new file mode 100644 index 000000000..b8fff5098 --- /dev/null +++ b/scripts/Dockerfile.build @@ -0,0 +1,27 @@ +FROM npm-cache:latest AS builder + +WORKDIR /workspace + +# Declare a build argument to control the minify flag +ARG ARGS + +# Copy source files except vscode so new patches are picked up without cache busting +COPY patches ./patches +COPY resources ./resources +COPY .git ./.git +COPY scripts/ ./scripts/ +COPY .pc ./.pc +COPY LICENSE . +COPY LICENSE-THIRD-PARTY . + +# Apply patches and build +RUN chmod +x scripts/docker-install.sh && \ + ./scripts/docker-install.sh -t "$(cat vscode/package.json | grep '"version"' | cut -d'"' -f4)" + +RUN echo "Build arguments: $ARGS" +RUN chmod +x scripts/create-code-editor-tarball.sh && \ + ./scripts/create-code-editor-tarball.sh -v "$(cat vscode/package.json | grep '"version"' | cut -d'"' -f4)" + +# Final stage: Minimal image with only the build artifacts to copy from +FROM scratch +COPY --from=builder /workspace/artifacts . \ No newline at end of file diff --git a/scripts/Dockerfile.build.cache b/scripts/Dockerfile.build.cache new file mode 100644 index 000000000..1085ed1e1 --- /dev/null +++ b/scripts/Dockerfile.build.cache @@ -0,0 +1,43 @@ +FROM ubuntu:24.04 AS base-image + +# Install required tools and Node.js +RUN apt-get update && apt-get install -y \ + git \ + python3 \ + python3-pip \ + build-essential \ + curl \ + quilt \ + pkg-config \ + libx11-dev \ + libxkbfile-dev \ + libsecret-1-dev \ + libkrb5-dev \ + libgssapi-krb5-2 \ + time \ + && rm -rf /var/lib/apt/lists/* + +# Install Node.js 22 (latest LTS) +RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \ + apt-get install -y nodejs + +# Stage 2: NPM Dependencies Cache +# This stage uses the pre-configured base-image to build the cache. +FROM base-image AS npm-cache + +WORKDIR /workspace + +COPY vscode ./vscode +COPY patches ./patches +COPY resources ./resources +COPY .git ./.git +COPY scripts/postinstall.sh ./scripts/postinstall.sh +COPY scripts/docker-install.sh ./scripts/docker-install.sh +COPY scripts/copy-resources.sh ./scripts/copy-resources.sh +COPY .pc ./.pc +COPY LICENSE . +COPY LICENSE-THIRD-PARTY . + +# Apply patches and build +RUN chmod +x scripts/docker-install.sh && \ + ./scripts/docker-install.sh -t "$(cat vscode/package.json | grep '"version"' | cut -d'"' -f4)" \ No newline at end of file diff --git a/scripts/Dockerfile.dev b/scripts/Dockerfile.dev new file mode 100644 index 000000000..bbc34c3ec --- /dev/null +++ b/scripts/Dockerfile.dev @@ -0,0 +1,36 @@ +FROM ubuntu:24.04 AS base-image + +# Install required tools and Node.js +RUN apt-get update && apt-get install -y \ + git \ + python3 \ + python3-pip \ + build-essential \ + curl \ + quilt \ + pkg-config \ + libx11-dev \ + libxkbfile-dev \ + libsecret-1-dev \ + libkrb5-dev \ + libgssapi-krb5-2 \ + time \ + && rm -rf /var/lib/apt/lists/* + +# Install Node.js 22 (latest LTS) +RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \ + apt-get install -y nodejs + +# Stage 2: NPM Dependencies Cache +# This stage uses the pre-configured base-image to build the cache. +FROM base-image AS npm-cache + +# Install supervisord +RUN apt-get update && \ + apt-get install -y supervisor + +WORKDIR /workspace + +EXPOSE 8080 + +# Following is the entrypoint script for local runs: /workspace/scripts/run-code-editor-dev.sh \ No newline at end of file diff --git a/scripts/Dockerfile.run b/scripts/Dockerfile.run new file mode 100644 index 000000000..6ab0e2a17 --- /dev/null +++ b/scripts/Dockerfile.run @@ -0,0 +1,24 @@ +FROM public.ecr.aws/sagemaker/sagemaker-distribution:latest-cpu + +# Accept tarball name as build argument +ARG TARBALL=code-editor1.8.0b5.tar.gz + +# Switch to root to install +USER root + +# Copy and extract the code editor to /tmp +COPY .artifacts/${TARBALL} /tmp/ +RUN cd /tmp && tar -xzf ${TARBALL} + +# Move to final location and set permissions +RUN mv /tmp/sagemaker-code-editor /opt/ && \ + chmod +x /opt/sagemaker-code-editor/bin/code-server-oss + +# Add to PATH +ENV PATH="/opt/sagemaker-code-editor/bin:$PATH" + +# Expose port +EXPOSE 8000 + +# Run code-server-oss with host binding to all interfaces +CMD ["code-server-oss", "--host", "0.0.0.0", "--without-connection-token"] diff --git a/scripts/clean-cache.sh b/scripts/clean-cache.sh new file mode 100644 index 000000000..67dc4feb8 --- /dev/null +++ b/scripts/clean-cache.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +echo "๐Ÿงน Cleaning local build cache..." + +# Remove cache directory +if [ -d ".local-cache" ]; then + rm -rf .local-cache + echo "โœ… Cache directory removed" +fi + +# Remove node_modules +if [ -d "node_modules" ]; then + rm -rf node_modules + echo "โœ… Root node_modules removed" +fi + +# Remove vscode node_modules +if [ -d "vscode/node_modules" ]; then + rm -rf vscode/node_modules + echo "โœ… VS Code node_modules removed" +fi + +# Remove build output +if [ -d "vscode-reh-web-linux-x64" ]; then + rm -rf vscode-reh-web-linux-x64 + echo "โœ… Build output removed" +fi + +echo "๐ŸŽ‰ All caches cleaned!" \ No newline at end of file diff --git a/scripts/create-code-editor-tarball.sh b/scripts/create-code-editor-tarball.sh new file mode 100755 index 000000000..8427c28cd --- /dev/null +++ b/scripts/create-code-editor-tarball.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +while getopts "v:m" opt; do + case $opt in + v) version="$OPTARG" + ;; + ?) usage; exit 1 ;; + esac +done + +if [[ -z $version ]]; then + echo "Please provide version using '-v'"; + exit 1 +fi + +VERSION=$version + +# Set current project root +PROJ_ROOT=$(pwd) + +pushd ${PROJ_ROOT}/vscode + +printf "\n======== Running gulp build task ========\n" +export DISABLE_V8_COMPILE_CACHE=1 +export UV_THREADPOOL_SIZE=4 +export ARCH_ALIAS=linux-x64 +export NODE_ENV=production +export MINIFY=true +node --max-old-space-size=16384 --optimize-for-size \ + ./node_modules/gulp/bin/gulp.js \ + "vscode-reh-web-${ARCH_ALIAS}${MINIFY:+-min}" + +popd + +TARBALL="sagemaker-code-editor-${VERSION}.tar.gz" +BUILD_DIR_PATH=.artifacts + +mv vscode-reh-web-linux-x64 sagemaker-code-editor +mkdir ${BUILD_DIR_PATH} +tar -czf ${BUILD_DIR_PATH}/${TARBALL} sagemaker-code-editor +sha256sum ${BUILD_DIR_PATH}/${TARBALL} diff --git a/scripts/create_code_editor_tarball.sh b/scripts/create_code_editor_tarball.sh deleted file mode 100644 index 6ae444092..000000000 --- a/scripts/create_code_editor_tarball.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash - -while getopts "v:" opt; do - case $opt in - v) version="$OPTARG" - ;; - \?) echo "Invalid option -$OPTARG" >&2 - exit 1 - ;; - esac - - case $OPTARG in - -*) echo "Option $opt needs a valid argument" - exit 1 - ;; - esac -done - -if [[ -z $version ]]; then - echo "Please provide version using '-v'"; - exit 1 -fi - -VERSION=$version -# Set current project root -PROJ_ROOT=$(pwd) - -mkdir -p ${PROJ_ROOT}/sagemaker-code-editor/code-editor${VERSION} -rm -rf ${PROJ_ROOT}/sagemaker-code-editor/code-editor${VERSION}/src -mkdir -p ${PROJ_ROOT}/sagemaker-code-editor/code-editor${VERSION}/src -cp -a ${PROJ_ROOT}/vscode/. ${PROJ_ROOT}/sagemaker-code-editor/code-editor${VERSION}/src/ -rm -rf ${PROJ_ROOT}/sagemaker-code-editor/code-editor${VERSION}.tar.gz -cd ${PROJ_ROOT}/sagemaker-code-editor -tar -czf code-editor${VERSION}.tar.gz code-editor${VERSION} -cd ${PROJ_ROOT} -cp ${PROJ_ROOT}/sagemaker-code-editor/code-editor${VERSION}.tar.gz ${PROJ_ROOT}/ -rm -rf ${PROJ_ROOT}/sagemaker-code-editor -sha256sum ${PROJ_ROOT}/code-editor${VERSION}.tar.gz diff --git a/scripts/docker-install.sh b/scripts/docker-install.sh new file mode 100755 index 000000000..38ccb5bf6 --- /dev/null +++ b/scripts/docker-install.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# ONLY FOR LOCAL BUILD USECASE +# This script is intended to be run inside the docker container +# It will setup the environment and build the project within the container + +# set +e to prevent quilt from exiting when no patches popped +set +e + +# Set current project root +PROJ_ROOT=$(pwd) + +# Clean out patches +printf "\n======== Cleaning out patches ========\n" +quilt pop -a +rm -rf .pc + +# re-enable -e to allow exiting on error +set -e + +# make sure module is current +printf "\n======== Updating submodule ========\n" +git submodule update --init + +# Apply patches +printf "\n======== Applying patches ========\n" + +if [ -d patches ] && [ "$(ls -A patches)" ]; then + { + quilt push -a --leave-rejects --color=auto + } || { + printf "\nPatching error, review logs!\n" + find ./vscode -name "*.rej" + exit 1 + } +fi + + +# Generate Licenses +printf "\n======== Generate Licenses ========\n" +cd ${PROJ_ROOT}/vscode +cp LICENSE.txt LICENSE.vscode.txt +cp ThirdPartyNotices.txt LICENSE-THIRD-PARTY.vscode.txt +cp ../LICENSE-THIRD-PARTY . + +cd ${PROJ_ROOT} +# Comment out breaking lines in postinstall.js +printf "\n======== Comment out breaking git config lines in postinstall.js ========\n" +sh ${PROJ_ROOT}/scripts/postinstall.sh + +# Copy resources +printf "\n======== Copy resources ========\n" +${PROJ_ROOT}/scripts/copy-resources.sh + +# Build the project +printf "\n======== Building project in ${PROJ_ROOT}/vscode ========\n" +cd ${PROJ_ROOT}/vscode +npm config set cache ~/.npm --global +npm install -g node-gyp +npm install --use-cache --no-audit --no-fund --prefer-offline --verbose --no-optional +npm run download-builtin-extensions --prefer-offline --verbose diff --git a/scripts/install.sh b/scripts/install.sh old mode 100644 new mode 100755 index cc7a78afd..246c81c02 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,26 +1,27 @@ #!/bin/bash -while getopts "v:" opt; do - case $opt in - v) version="$OPTARG" - ;; - \?) echo "Invalid option -$OPTARG" >&2 - exit 1 - ;; - esac +usage() { + printf """ +Usage: $0 [-t ] [-v] [-h] + +Otions: + -t Create a tarball with the specified version + -v Enable verbose output + -h Show this help message +""" +} - case $OPTARG in - -*) echo "Option $opt needs a valid argument" - exit 1 - ;; +while getopts "t:hv" opt; do + case $opt in + t) version="$OPTARG" + CREATE_TARBALL=true ;; + v) VERBOSE_ARG="--verbose" ;; + h) usage; exit 0 ;; + :) printf "Error: -${OPTARG} requires an argument.\n" >&2; exit 1 ;; + ?) usage; exit 1 ;; esac done -if [[ -z $version ]]; then - echo "Please provide version using '-v'"; - exit 1 -fi - VERSION=$version # set +e to prevent quilt from exiting when no patches popped @@ -48,11 +49,11 @@ git submodule update --init # Apply patches printf "\n======== Applying patches ========\n" { - quilt push -a --leave-rejects --color=auto + quilt push -a --leave-rejects --color=auto } || { - printf "\nPatching error, review logs!\n" - find ./vscode -name "*.rej" - exit 1 + printf "\nPatching error, review logs!\n" + find ./vscode -name "*.rej" + exit 1 } @@ -68,19 +69,28 @@ cd ${PROJ_ROOT} printf "\n======== Comment out breaking git config lines in postinstall.js ========\n" sh ${PROJ_ROOT}/scripts/postinstall.sh -# Build tarball for conda feedstock from vscode dir -printf "\n======== Build Tarball for Conda Feedstock ========\n" -bash ${PROJ_ROOT}/scripts/create_code_editor_tarball.sh -v ${VERSION} +# Delete node_modules to prevent node-gyp build error and reduce tarball size +printf "\n======== Deleting vscode/node_modules ========\n" +find "${PROJ_ROOT}/vscode" -name "node_modules" -type d -prune -exec rm -rf '{}' + + +# Create tarball +if [ "$CREATE_TARBALL" = true ]; then + # Build tarball for conda feedstock from vscode dir + printf "\n======== Build Tarball for Conda Feedstock ========\n" + bash ${PROJ_ROOT}/scripts/create_code_editor_tarball.sh -v ${VERSION} +fi # Copy resources printf "\n======== Copy resources ========\n" -sh ${PROJ_ROOT}/scripts/copy-resources.sh +${PROJ_ROOT}/scripts/copy-resources.sh -# Delete node_modules to prevent node-gyp build error -printf "\n======== Deleting vscode/node_modules ========\n" -rm -rf "${PROJ_ROOT}/vscode/node_modules" +# Copy patched files to patches-vscode +cp -R vscode/* patched-vscode/ # Build the project printf "\n======== Building project in ${PROJ_ROOT}/vscode ========\n" -yarn --cwd "${PROJ_ROOT}/vscode" install --pure-lockfile --verbose -yarn --cwd "${PROJ_ROOT}/vscode" download-builtin-extensions +# npm --cwd "${PROJ_ROOT}/vscode" install --pure-lockfile ${VERBOSE_ARG} +# npm --cwd "${PROJ_ROOT}/vscode" download-builtin-extensions +cd ${PROJ_ROOT}/vscode +npm install +npm download-builtin-extensions diff --git a/scripts/postinstall.sh b/scripts/postinstall.sh old mode 100644 new mode 100755 index 6ec20d75e..cbeb5f9f7 --- a/scripts/postinstall.sh +++ b/scripts/postinstall.sh @@ -15,7 +15,19 @@ if [ ! -f "$POSTINSTALL_JS_PATH" ]; then exit 1 fi -# Use sed to comment out the specific lines -sed -i '' '/cp\.execSync('"'"'git config .*);/s/^/\/\/ /' "$POSTINSTALL_JS_PATH" +# set +e to prevent script from exiting when not on macOS +set +e + +# Check if on macOS +system_profiler SPSoftwareDataType + +# Run with different arguments depending on the OS +if [ $? -eq 0 ]; then + # Use sed to comment out the specific lines + sed -i '' '/cp\.execSync('"'"'git config .*);/s/^/\/\/ /' "$POSTINSTALL_JS_PATH" +else + # Use sed to comment out the specific lines + sed -i '/cp\.execSync('"'"'git config .*);/s/^/\/\/ /' "$POSTINSTALL_JS_PATH" +fi echo "Specified git config lines have been commented out in $POSTINSTALL_JS_PATH." diff --git a/scripts/run-code-editor-dev.sh b/scripts/run-code-editor-dev.sh new file mode 100755 index 000000000..6370af7ef --- /dev/null +++ b/scripts/run-code-editor-dev.sh @@ -0,0 +1,102 @@ +#!/bin/bash + +# ONLY FOR LOCAL DEV USECASE +# This script is intended to be run inside the docker container +# It will setup the environment and start the code editor in watch mode within the container + +set +e + +# Set current project root +PROJ_ROOT=$(pwd) + +# Clean out patches +printf "\n======== Cleaning out patches ========\n" +quilt pop -a +rm -rf .pc + +# re-enable -e to allow exiting on error +set -e + +# make sure module is current +printf "\n======== Updating submodule ========\n" +git config --global --add safe.directory /workspace +git submodule update --init + +# Apply patches +printf "\n======== Applying patches ========\n" +{ + quilt push -a --leave-rejects --color=auto +} || { + printf "\nPatching error, review logs!\n" + find ./vscode -name "*.rej" + exit 1 +} + +cd ${PROJ_ROOT} +# Comment out breaking lines in postinstall.js +printf "\n======== Comment out breaking git config lines in postinstall.js ========\n" +sh ${PROJ_ROOT}/scripts/postinstall.sh + +# Copy resources +printf "\n======== Copy resources ========\n" +${PROJ_ROOT}/scripts/copy-resources.sh + +# Build the project +printf "\n======== Installing dependencies ========\n" +cd ${PROJ_ROOT}/vscode + +# Install dependencies +npm install +npm run download-builtin-extensions + +printf "\n======== Starting Code Editor and Watch process using supervisord ========\n" + +npm run watch & +sleep 300 + +# This sciript can be directly used to run code server without supervisord +# ./scripts/code-server.sh --host 0.0.0.0 --port 8000 --without-connection-token + +# Create supervisord config to run code server +cat > /etc/supervisor/supervisord.conf << EOF +[unix_http_server] +file=/var/run/supervisor.sock + +[supervisord] +logfile=/var/log/supervisord.log +pidfile=/var/run/supervisord.pid +nodaemon=false + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +[supervisorctl] +serverurl=unix:///var/run/supervisor.sock + +[include] +files = /etc/supervisor/conf.d/*.conf +EOF + +# Start supervisor daemon +/usr/bin/supervisord -c /etc/supervisor/supervisord.conf & +sleep 2 + +# Create supervisor config file for code server +cat > /etc/supervisor/conf.d/vscode-server.conf << EOF +[program:vscode-server] +directory=/workspace/vscode +command=./scripts/code-server.sh --host 0.0.0.0 --port 8000 --without-connection-token +autostart=true +autorestart=true +stderr_logfile=/var/log/vscode-server.err.log +stdout_logfile=/var/log/vscode-server.out.log +EOF + +# Reload supervisor config +supervisorctl reread +supervisorctl update + +supervisorctl start vscode-server + +# Keep container running and show logs +tail -f /var/log/vscode-*.log \ No newline at end of file diff --git a/scripts/test-local.sh b/scripts/test-local.sh new file mode 100644 index 000000000..4657a390f --- /dev/null +++ b/scripts/test-local.sh @@ -0,0 +1,104 @@ +#!/bin/bash +set -e + +echo "๐Ÿš€ Testing sagemaker-code-editor locally..." + +# Check if in correct directory +if [ ! -f "package.json" ]; then + echo "โŒ Run this from sagemaker-code-editor root directory" + exit 1 +fi + +# Create cache directory +CACHE_DIR=".local-cache" +mkdir -p "$CACHE_DIR" + +# Check if we need to install root dependencies +ROOT_CACHE="$CACHE_DIR/root-node_modules.tar.gz" +if [ ! -f "$ROOT_CACHE" ] || [ "package.json" -nt "$ROOT_CACHE" ]; then + echo "๐Ÿ“ฆ Installing root dependencies..." + npm install + if [ -d "node_modules" ]; then + echo "๐Ÿ’พ Caching root node_modules..." + tar czf "$ROOT_CACHE" node_modules/ + fi +else + echo "๐Ÿ“ฆ Restoring cached root dependencies..." + tar xzf "$ROOT_CACHE" +fi + +# Apply patches +if [ -d patches ] && [ "$(ls -A patches)" ]; then + echo "๐Ÿ”ง Applying patches..." + quilt push -a || echo "Patches already applied or no patches to apply" +fi + +# Build VS Code with caching +echo "๐Ÿ—๏ธ Building VS Code..." +cd vscode + +# Check vscode revision for cache key +VSCODE_REV=$(git rev-parse HEAD) +PATCHES_HASH=$(find ../patches -name "*.diff" -exec md5sum {} \; 2>/dev/null | md5sum | cut -d' ' -f1 || echo "no-patches") +VSCODE_CACHE="$CACHE_DIR/vscode-${VSCODE_REV}-${PATCHES_HASH}.tar.gz" + +# Check if we have cached vscode build +if [ -f "$VSCODE_CACHE" ] && [ -f "../vscode-reh-web-linux-x64/bin/code-server" ]; then + echo "๐Ÿ’พ Using cached VS Code build..." + cd .. +else + # Check if we need to install vscode dependencies + VSCODE_DEPS_CACHE="$CACHE_DIR/vscode-node_modules-${VSCODE_REV}.tar.gz" + if [ ! -f "$VSCODE_DEPS_CACHE" ] || [ "package.json" -nt "$VSCODE_DEPS_CACHE" ]; then + echo "๐Ÿ“ฆ Installing vscode dependencies..." + npm install + if [ -d "node_modules" ]; then + echo "๐Ÿ’พ Caching vscode node_modules..." + tar czf "../$VSCODE_DEPS_CACHE" node_modules/ + fi + else + echo "๐Ÿ“ฆ Restoring cached vscode dependencies..." + tar xzf "../$VSCODE_DEPS_CACHE" + fi + + # Get ripgrep version and handle it + VSCODE_RIPGREP_VERSION=$(jq -r '.dependencies."@vscode/ripgrep"' package.json) + mv package.json package.json.orig + jq 'del(.dependencies."@vscode/ripgrep")' package.json.orig > package.json + npm install + npm install --ignore-scripts "@vscode/ripgrep@${VSCODE_RIPGREP_VERSION}" + + # Build with memory optimization + echo "๐Ÿ—๏ธ Building with increased memory..." + NODE_OPTIONS="--max-old-space-size=6144" npx gulp vscode-reh-web-linux-x64-min + + cd .. + + # Cache the built vscode + if [ -d "vscode-reh-web-linux-x64" ]; then + echo "๐Ÿ’พ Caching VS Code build..." + tar czf "$VSCODE_CACHE" vscode-reh-web-linux-x64/ + fi +fi + +echo "โœ… Build complete!" +echo "๐ŸŒ Starting server on http://localhost:8080" +echo "Press Ctrl+C to stop" + +# Find the correct executable path +if [ -f "./vscode-reh-web-linux-x64/bin/code-server" ]; then + EXEC_PATH="./vscode-reh-web-linux-x64/bin/code-server" +elif [ -f "./vscode-reh-web-linux-x64/bin/remote-cli/code-server" ]; then + EXEC_PATH="./vscode-reh-web-linux-x64/bin/remote-cli/code-server" +elif [ -f "./vscode-reh-web-linux-x64/out/server-main.js" ]; then + echo "Starting with Node.js..." + node ./vscode-reh-web-linux-x64/out/server-main.js --host 0.0.0.0 --port 8080 --without-connection-token + exit 0 +else + echo "โŒ Could not find executable. Checking build output..." + find ./vscode-reh-web-linux-x64 -name "*server*" -type f + exit 1 +fi + +# Start server +$EXEC_PATH --host 0.0.0.0 --port 8080 --without-connection-token \ No newline at end of file diff --git a/vscode b/vscode index 611f9bfce..2901c5ac6 160000 --- a/vscode +++ b/vscode @@ -1 +1 @@ -Subproject commit 611f9bfce64f25108829dd295f54a6894e87339d +Subproject commit 2901c5ac6db8a986a5666c3af51ff804d05af0d4