diff --git a/.github/actions/vscode/calculate-artifact-name/action.yml b/.github/actions/vscode/calculate-artifact-name/action.yml new file mode 100644 index 00000000..e13ed32c --- /dev/null +++ b/.github/actions/vscode/calculate-artifact-name/action.yml @@ -0,0 +1,34 @@ +name: 'Calculate Artifact Name' +description: 'Calculate artifact name with run number and mode suffix' + +inputs: + artifact-name: + description: 'Base artifact name or pre-calculated name' + required: true + dry-run: + description: 'Whether this is a dry-run mode' + required: false + default: 'false' + run-number: + description: 'GitHub run number (defaults to github.run_number)' + required: false + default: '${{ github.run_number }}' + +outputs: + artifact-name: + description: 'The calculated artifact name' + value: ${{ steps.calc.outputs.artifact-name }} + +runs: + using: 'composite' + steps: + - name: Calculate artifact name + id: calc + shell: bash + run: | + # Only treat as already set if artifact-name ends with -dry-run or -release + if [[ "${{ inputs.artifact-name }}" =~ -dry-run$ ]] || [[ "${{ inputs.artifact-name }}" =~ -release$ ]]; then + echo "artifact-name=${{ inputs.artifact-name }}" >> $GITHUB_OUTPUT + else + echo "artifact-name=${{ format('{0}-{1}-{2}', inputs.artifact-name, inputs.run-number, inputs.dry-run == 'true' && 'dry-run' || 'release') }}" >> $GITHUB_OUTPUT + fi \ No newline at end of file diff --git a/.github/actions/vscode/check-ci-status/action.yml b/.github/actions/vscode/check-ci-status/action.yml new file mode 100644 index 00000000..6a0b3a67 --- /dev/null +++ b/.github/actions/vscode/check-ci-status/action.yml @@ -0,0 +1,100 @@ +name: Check CI Status +description: > + Verifies that CI checks passed for a given commit SHA before promotion. + Fails if any required check did not succeed. + +inputs: + commit-sha: + description: 'Commit SHA to check CI status for' + required: true + token: + description: 'GitHub token with repo read access' + required: true + required-checks: + description: > + Comma-separated list of check names that must have succeeded. + If empty, all non-skipped check-runs must have conclusion "success". + required: false + default: '' + +runs: + using: composite + steps: + - name: Verify CI checks passed + shell: bash + env: + GH_TOKEN: ${{ inputs.token }} + COMMIT_SHA: ${{ inputs.commit-sha }} + REQUIRED_CHECKS: ${{ inputs.required-checks }} + REPO: ${{ github.repository }} + run: | + echo "Checking CI status for commit $COMMIT_SHA in $REPO..." + + # Fetch all check-runs for the commit (paginate up to 100) + CHECK_RUNS=$(gh api \ + "repos/$REPO/commits/$COMMIT_SHA/check-runs" \ + --paginate \ + --jq '.check_runs[] | {name: .name, status: .status, conclusion: .conclusion}' \ + 2>&1) + + if [ -z "$CHECK_RUNS" ]; then + echo "No check-runs found for commit $COMMIT_SHA" + echo "Cannot verify CI status — failing to prevent untested promotion" + exit 1 + fi + + echo "Check-runs found:" + echo "$CHECK_RUNS" | jq -r '" \(.name): status=\(.status) conclusion=\(.conclusion)"' + + FAILED=0 + + if [ -n "$REQUIRED_CHECKS" ]; then + # Only validate the specified checks + IFS=',' read -ra CHECKS <<< "$REQUIRED_CHECKS" + for CHECK in "${CHECKS[@]}"; do + CHECK=$(echo "$CHECK" | xargs) # trim whitespace + CONCLUSION=$(echo "$CHECK_RUNS" | jq -r --arg name "$CHECK" \ + 'select(.name == $name) | .conclusion' | head -1) + if [ "$CONCLUSION" != "success" ]; then + echo "FAIL: required check '$CHECK' has conclusion '$CONCLUSION' (expected 'success')" + FAILED=1 + else + echo "PASS: required check '$CHECK' succeeded" + fi + done + else + # Validate all non-skipped check-runs + while IFS= read -r RUN; do + NAME=$(echo "$RUN" | jq -r '.name') + STATUS=$(echo "$RUN" | jq -r '.status') + CONCLUSION=$(echo "$RUN" | jq -r '.conclusion') + + # Skip queued/in-progress (treat as not-yet-run, which is a failure) + if [ "$STATUS" != "completed" ]; then + echo "FAIL: check '$NAME' is not completed (status=$STATUS)" + FAILED=1 + continue + fi + + # Allow skipped checks (neutral conclusion) + if [ "$CONCLUSION" = "skipped" ] || [ "$CONCLUSION" = "neutral" ]; then + echo "SKIP: check '$NAME' was skipped — ignoring" + continue + fi + + if [ "$CONCLUSION" != "success" ]; then + echo "FAIL: check '$NAME' has conclusion '$CONCLUSION'" + FAILED=1 + fi + done < <(echo "$CHECK_RUNS" | jq -c '.') + fi + + if [ "$FAILED" -eq 1 ]; then + echo "" + echo "CI quality gate FAILED for commit $COMMIT_SHA" + echo "Promotion blocked. Fix failing checks before retrying." + exit 1 + fi + + echo "" + echo "CI quality gate PASSED for commit $COMMIT_SHA" diff --git a/.github/actions/vscode/detect-packages/action.yml b/.github/actions/vscode/detect-packages/action.yml new file mode 100644 index 00000000..a4f08d34 --- /dev/null +++ b/.github/actions/vscode/detect-packages/action.yml @@ -0,0 +1,59 @@ +name: 'Detect Packages' +description: 'Dynamically discovers NPM packages and VS Code extensions in a monorepo' + +inputs: + packages-root: + description: 'Root directory containing packages (default: packages)' + required: false + default: 'packages' + +outputs: + npm-packages: + description: 'Comma-separated list of NPM package names' + value: ${{ steps.packages.outputs.npm-packages }} + extensions: + description: 'Comma-separated list of VS Code extension names' + value: ${{ steps.packages.outputs.extensions }} + extension-paths: + description: 'Extension package paths for publishing' + value: ${{ steps.packages.outputs.extension-paths }} + +runs: + using: 'composite' + steps: + - name: Detect packages and extensions + id: packages + shell: bash + env: + PACKAGES_ROOT: ${{ inputs.packages-root }} + run: | + # Get NPM packages (packages with package.json but no publisher) + NPM_PACKAGES="" + EXTENSIONS="" + EXTENSION_PATHS="" + + for pkg in $PACKAGES_ROOT/*/; do + PKG_NAME=$(basename "$pkg") + if [ -f "$pkg/package.json" ]; then + if grep -q '"publisher"' "$pkg/package.json"; then + # It's a VS Code extension + EXTENSIONS="$EXTENSIONS,$PKG_NAME" + EXTENSION_PATHS="$EXTENSION_PATHS,$pkg" + else + # It's an NPM package + NPM_PACKAGES="$NPM_PACKAGES,$PKG_NAME" + fi + fi + done + + # Remove leading commas + NPM_PACKAGES=${NPM_PACKAGES#,} + EXTENSIONS=${EXTENSIONS#,} + EXTENSION_PATHS=${EXTENSION_PATHS#,} + + echo "npm-packages=$NPM_PACKAGES" >> $GITHUB_OUTPUT + echo "extensions=$EXTENSIONS" >> $GITHUB_OUTPUT + echo "extension-paths=$EXTENSION_PATHS" >> $GITHUB_OUTPUT + + echo "Detected NPM packages: $NPM_PACKAGES" + echo "Detected VS Code extensions: $EXTENSIONS" diff --git a/.github/actions/vscode/download-vsix-artifacts/action.yml b/.github/actions/vscode/download-vsix-artifacts/action.yml new file mode 100644 index 00000000..b7d3b08a --- /dev/null +++ b/.github/actions/vscode/download-vsix-artifacts/action.yml @@ -0,0 +1,30 @@ +name: 'Download VSIX Artifacts' +description: 'Downloads and finds VSIX artifacts for publishing workflows' + +inputs: + artifact-name: + description: 'Name for the VSIX artifacts' + required: false + default: 'vsix-packages' + type: string + +outputs: + vsix_files: + description: 'JSON array of VSIX file paths' + value: ${{ steps.find_vsix.outputs.vsix_files }} + +runs: + using: composite + steps: + - name: Download VSIX artifacts + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.artifact-name }} + path: ./vsix-artifacts + + - name: Find VSIX files + id: find_vsix + shell: bash + run: | + VSIX_FILES=$(find ./vsix-artifacts -name "*.vsix" | jq -R -s -c 'split("\n")[:-1]') + echo "vsix_files=$VSIX_FILES" >> $GITHUB_OUTPUT \ No newline at end of file diff --git a/.github/actions/vscode/npm-install-with-retries/action.yml b/.github/actions/vscode/npm-install-with-retries/action.yml new file mode 100644 index 00000000..d92cd1ca --- /dev/null +++ b/.github/actions/vscode/npm-install-with-retries/action.yml @@ -0,0 +1,16 @@ +name: npm-install-with-retries +description: "wraps npm ci with retries/timeout to handle network failures" +inputs: + ignore-scripts: + default: 'false' + description: "Skip pre/post install scripts" +runs: + using: composite + steps: + - name: Set npm fetch timeout + shell: bash + run: npm config set fetch-timeout 600000 + - name: npm ci + uses: salesforcecli/github-workflows/.github/actions/retry@main + with: + command: npm ci ${{ inputs.ignore-scripts == 'true' && '--ignore-scripts' || '' }} \ No newline at end of file diff --git a/.github/actions/vscode/parse-pr-title/action.yml b/.github/actions/vscode/parse-pr-title/action.yml new file mode 100644 index 00000000..44c4bb69 --- /dev/null +++ b/.github/actions/vscode/parse-pr-title/action.yml @@ -0,0 +1,46 @@ +name: Parse Release PR Title +description: | + Parse a release PR title in the form "Release PR for as " + and emit version + channel as outputs. + + Channel is one of: nightly | pre-release | stable | rc. + Fails with a clear error message if the title does not match the expected pattern. + +inputs: + title: + required: true + description: | + The release PR title to parse, e.g. "Release PR for 65.9.0 as nightly". + +outputs: + version: + description: 'Parsed semver (e.g. 65.9.0)' + value: ${{ steps.parse.outputs.version }} + channel: + description: 'Parsed channel token (e.g. nightly)' + value: ${{ steps.parse.outputs.channel }} + +runs: + using: composite + steps: + - name: Parse title + id: parse + shell: bash + env: + TITLE: ${{ inputs.title }} + run: | + set -euo pipefail + # Pattern: "Release PR for as " + # X.Y.Z is a 3-segment semver. Channel is restricted to a closed set + # to reject pathological tokens (e.g. trailing/double dashes). + regex='^Release PR for ([0-9]+\.[0-9]+\.[0-9]+) as (nightly|pre-release|stable|rc)$' + if [[ "$TITLE" =~ $regex ]]; then + version="${BASH_REMATCH[1]}" + channel="${BASH_REMATCH[2]}" + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "channel=$channel" >> "$GITHUB_OUTPUT" + echo "Parsed title: version=$version channel=$channel" + else + echo "::error::Could not parse release PR title. Expected 'Release PR for as ' but got: $TITLE" + exit 1 + fi diff --git a/.github/actions/vscode/publish-vsix/action.yml b/.github/actions/vscode/publish-vsix/action.yml new file mode 100644 index 00000000..5a77f448 --- /dev/null +++ b/.github/actions/vscode/publish-vsix/action.yml @@ -0,0 +1,164 @@ +name: "Publish VSIX" +description: "Publishes VSIX files to a marketplace with dry-run support" + +inputs: + vsix-path: + description: "Path to the VSIX file to publish" + required: true + publish-tool: + description: "Publishing tool to use" + required: true + pre-release: + description: "Publish as pre-release version" + required: false + default: "false" + dry-run: + description: "Run in dry-run mode" + required: false + default: "false" + +runs: + using: composite + steps: + - name: Validate inputs + shell: bash + run: | + # Validate VSIX path exists + if [ ! -f "${{ inputs.vsix-path }}" ]; then + echo "❌ Error: VSIX file not found at ${{ inputs.vsix-path }}" + exit 1 + fi + + # Validate VSIX file extension + if [[ ! "${{ inputs.vsix-path }}" =~ \.vsix$ ]]; then + echo "❌ Error: File must have .vsix extension" + exit 1 + fi + + # Validate publish tool + if [[ ! "${{ inputs.publish-tool }}" =~ ^(ovsx|vsce)$ ]]; then + echo "❌ Error: Invalid publish tool: ${{ inputs.publish-tool }}" + exit 1 + fi + + echo "✅ Input validation passed" + + - name: Validate VSIX has pre-release marker (when publishing as pre-release) + if: inputs.pre-release == 'true' + shell: bash + env: + VSIX_PATH: ${{ inputs.vsix-path }} + run: | + set -euo pipefail + # OpenVSX silently ignores --pre-release at publish time if the VSIX + # manifest does not declare Microsoft.VisualStudio.Code.PreRelease="true". + # The publish succeeds but ships as Stable. Catch it pre-publish so we + # never corrupt the marketplace listing. + if ! unzip -p "$VSIX_PATH" extension.vsixmanifest | grep -q 'Microsoft.VisualStudio.Code.PreRelease'; then + echo "::error file=$VSIX_PATH::VSIX manifest is missing Microsoft.VisualStudio.Code.PreRelease. Refusing to publish as pre-release — would silently ship as Stable on OpenVSX. Re-package the VSIX with the --pre-release flag (e.g. via the shared vscode-package.yml workflow with pre-release: 'true', or 'vsce package --pre-release' / '@vscode/vsce package --pre-release' directly)." + exit 1 + fi + echo "✅ Pre-release marker present in $VSIX_PATH" + + - name: Audit publish attempt + shell: bash + run: | + # Create audit log entry + AUDIT_LOG="/tmp/publish_audit.log" + TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + ACTOR="${{ github.actor }}" + REPO="${{ github.repository }}" + RUN_ID="${{ github.run_id }}" + WORKFLOW="${{ github.workflow }}" + + # Get file info for audit + FILE_SIZE=$(stat -c%s "${{ inputs.vsix-path }}" 2>/dev/null || stat -f%z "${{ inputs.vsix-path }}" 2>/dev/null || echo "unknown") + FILE_HASH=$(sha256sum "${{ inputs.vsix-path }}" 2>/dev/null | cut -d' ' -f1 || echo "unknown") + + # Log audit information + echo "[$TIMESTAMP] PUBLISH_ATTEMPT: actor=$ACTOR, repo=$REPO, run_id=$RUN_ID, workflow=$WORKFLOW, tool=${{ inputs.publish-tool }}, file=${{ inputs.vsix-path }}, size=$FILE_SIZE, hash=$FILE_HASH, pre_release=${{ inputs.pre-release }}, dry_run=${{ inputs.dry-run }}" >> "$AUDIT_LOG" + + # Also log to GitHub Actions output for visibility + echo "🔍 AUDIT: Publish attempt logged - $TIMESTAMP" + echo " Actor: $ACTOR" + echo " Repository: $REPO" + echo " Run ID: $RUN_ID" + echo " Workflow: $WORKFLOW" + echo " Tool: ${{ inputs.publish-tool }}" + echo " File: ${{ inputs.vsix-path }}" + echo " Size: $FILE_SIZE bytes" + echo " Hash: $FILE_HASH" + echo " Pre-release: ${{ inputs.pre-release }}" + echo " Dry-run: ${{ inputs.dry-run }}" + + - name: Publish VSIX + shell: bash + run: | + echo "Publishing ${{ inputs.vsix-path }}" + + # Calculate marketplace name based on publish tool + if [ "${{ inputs.publish-tool }}" = "ovsx" ]; then + MARKETPLACE_NAME="Open VSX Registry" + TOKEN_ENV="OVSX_PAT" + else + MARKETPLACE_NAME="Visual Studio Marketplace" + TOKEN_ENV="VSCE_PERSONAL_ACCESS_TOKEN" + fi + + PRE_RELEASE_FLAG="" + if [ "${{ inputs.pre-release }}" = "true" ]; then + PRE_RELEASE_FLAG="--pre-release" + echo "Would publish as pre-release version" + fi + + # Mask token in logs for security + TOKEN_MASK="***" + + if [ "${{ inputs.dry-run }}" = "true" ]; then + echo "🔍 DRY RUN MODE - Would publish to $MARKETPLACE_NAME:" + echo " VSIX: ${{ inputs.vsix-path }}" + echo " Pre-release: ${{ inputs.pre-release }}" + + if [ "${{ inputs.publish-tool }}" = "ovsx" ]; then + echo " Command: npx ovsx publish \"${{ inputs.vsix-path }}\" -p $TOKEN_MASK $PRE_RELEASE_FLAG" + else + echo " Command: npx @vscode/vsce publish --packagePath \"${{ inputs.vsix-path }}\" --skip-duplicate $PRE_RELEASE_FLAG" + fi + echo "✅ Dry run completed - no actual publish performed" + else + echo "Publishing VSIX: ${{ inputs.vsix-path }}" + + # Verify token is available + if [ -z "${!TOKEN_ENV}" ]; then + echo "❌ Error: $TOKEN_ENV environment variable is not set" + exit 1 + fi + + if [ "${{ inputs.publish-tool }}" = "vsce" ]; then + export VSCE_PAT="${!TOKEN_ENV}" # ensure the expected env var is set + npx @vscode/vsce publish --packagePath "${{ inputs.vsix-path }}" --skip-duplicate $PRE_RELEASE_FLAG + else + npx ovsx publish "${{ inputs.vsix-path }}" -p "${!TOKEN_ENV}" --skip-duplicate $PRE_RELEASE_FLAG + fi + + echo "✅ Successfully published to $MARKETPLACE_NAME" + fi + + - name: Audit publish result + shell: bash + if: inputs.dry-run != 'true' + run: | + # Log the result of the publish attempt + AUDIT_LOG="/tmp/publish_audit.log" + TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + ACTOR="${{ github.actor }}" + REPO="${{ github.repository }}" + RUN_ID="${{ github.run_id }}" + + if [ $? -eq 0 ]; then + echo "[$TIMESTAMP] PUBLISH_SUCCESS: actor=$ACTOR, repo=$REPO, run_id=$RUN_ID, tool=${{ inputs.publish-tool }}, file=${{ inputs.vsix-path }}" >> "$AUDIT_LOG" + echo "✅ AUDIT: Publish successful - $TIMESTAMP" + else + echo "[$TIMESTAMP] PUBLISH_FAILURE: actor=$ACTOR, repo=$REPO, run_id=$RUN_ID, tool=${{ inputs.publish-tool }}, file=${{ inputs.vsix-path }}" >> "$AUDIT_LOG" + echo "❌ AUDIT: Publish failed - $TIMESTAMP" + fi diff --git a/.github/workflows/test-vscode-package.yml b/.github/workflows/test-vscode-package.yml new file mode 100644 index 00000000..9ca180bd --- /dev/null +++ b/.github/workflows/test-vscode-package.yml @@ -0,0 +1,51 @@ +name: Test VS Code Extension CI Package + +on: + push: + branches: + - feat/add-vscode-extension-ci + paths: + - 'packages/vscode-extension-ci/**' + - '.github/workflows/test-vscode-package.yml' + workflow_dispatch: + +jobs: + build-and-test: + name: Build NPM Package + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '22.x' + + - name: Install dependencies + working-directory: packages/vscode-extension-ci + run: npm install + + - name: Build package + working-directory: packages/vscode-extension-ci + run: npm run build + + - name: Verify CLI exists + working-directory: packages/vscode-extension-ci + run: | + if [ ! -f dist/cli.js ]; then + echo "Error: dist/cli.js not found" + exit 1 + fi + echo "✓ CLI built successfully" + + - name: Test CLI help + working-directory: packages/vscode-extension-ci + run: node dist/cli.js --help + + - name: List available commands + working-directory: packages/vscode-extension-ci + run: | + echo "Checking for required commands..." + node dist/cli.js --help | grep -E "ext-package-selector|ext-change-detector|ext-build-type" + echo "✓ All required commands found" diff --git a/.github/workflows/test-vscode-workflows-integration.yml b/.github/workflows/test-vscode-workflows-integration.yml new file mode 100644 index 00000000..319fb599 --- /dev/null +++ b/.github/workflows/test-vscode-workflows-integration.yml @@ -0,0 +1,60 @@ +name: Test VS Code Workflows Integration + +on: + workflow_dispatch: + push: + branches: + - feat/add-vscode-extension-ci + paths: + - '.github/workflows/test-vscode-workflows-integration.yml' + - 'packages/vscode-extension-ci/**' + +jobs: + test-with-apex-language-support: + name: Test with apex-language-support + runs-on: ubuntu-latest + steps: + - name: Checkout github-workflows + uses: actions/checkout@v6 + with: + path: toolkit + + - name: Checkout apex-language-support + uses: actions/checkout@v6 + with: + repository: forcedotcom/apex-language-support + path: test-repo + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '22.x' + + - name: Install dependencies in test repo + working-directory: test-repo + run: npm ci + + - name: Build CLI package + working-directory: toolkit/packages/vscode-extension-ci + run: | + npm install + npm run build + + - name: Test ext-package-selector + working-directory: test-repo + run: | + node ../toolkit/packages/vscode-extension-ci/dist/cli.js ext-package-selector + echo "✓ Package selector works" + + - name: Test ext-build-type + working-directory: test-repo + run: | + node ../toolkit/packages/vscode-extension-ci/dist/cli.js ext-build-type + echo "✓ Build type detection works" + + - name: Build VSIX package + working-directory: test-repo/packages/apex-lsp-vscode-extension + run: | + npm run package + ls -la *.vsix + echo "✓ VSIX packaging successful" diff --git a/.github/workflows/vscode-automerge-nightly-pr.yml b/.github/workflows/vscode-automerge-nightly-pr.yml new file mode 100644 index 00000000..8473a173 --- /dev/null +++ b/.github/workflows/vscode-automerge-nightly-pr.yml @@ -0,0 +1,116 @@ +name: Automerge Nightly PR + +# Reusable workflow that automerges a "Release PR for as nightly" +# pull request once tests pass. Uses the `cli:release:automerge` command +# from @salesforce/plugin-release-management. +# +# Usage from consuming repository (pin to a version tag like @v1, not @main): +# on: +# pull_request: +# types: [labeled] +# jobs: +# automerge: +# if: | +# github.event.label.name == 'nightly-automerge' && +# startsWith(github.event.pull_request.title, 'Release PR for') && +# endsWith(github.event.pull_request.title, 'as nightly') +# uses: salesforcecli/github-workflows/.github/workflows/vscode-automerge-nightly-pr.yml@v1 +# with: +# pr-number: ${{ github.event.pull_request.number }} +# bot-user: +# secrets: +# bot-github-token: ${{ secrets. }} +# +# Required secret: +# bot-github-token (no default) — PAT for the bot account that authored +# the PR and will perform the merge. Needs `repo` scope. The PR author +# must match the bot identity owning this PAT. + +on: + workflow_call: + inputs: + pr-number: + description: 'Pull request number to automerge' + required: true + type: number + bot-user: + description: | + GitHub username of the bot that authored the PR. Passed to the + automerge command for verification. + required: true + type: string + max-attempts: + description: 'Maximum number of automerge retry attempts' + required: false + default: 30 + type: number + retry-wait-seconds: + description: 'Seconds to wait between retry attempts' + required: false + default: 120 + type: number + dry-run: + description: 'Pass --dry-run to sf-release; verifies PR but does not merge. Use for cross-repo testing.' + required: false + default: 'false' + type: string + secrets: + bot-github-token: + description: 'PAT for the bot account performing the merge. See header docstring.' + required: true + +permissions: + contents: write + pull-requests: write + +jobs: + automerge: + name: Automerge Nightly Release PR + runs-on: ubuntu-latest + steps: + - name: Checkout (for local actions) + uses: actions/checkout@v6 + + # Defense in depth: validate by querying the PR directly via GitHub's API. + # The previous approach checked `github.event.pull_request.*`, which only + # populates on `pull_request: [labeled]` events. If a caller invoked this + # via `workflow_dispatch` or another event, that `if:` silently no-op'd + # and marked the job green. This step always runs and explicitly fails + # if the PR doesn't match the release-PR sentinel. + - name: Verify PR title and label sentinel + env: + GH_TOKEN: ${{ secrets.bot-github-token }} + PR_NUMBER: ${{ inputs.pr-number }} + run: | + set -euo pipefail + PR_DATA=$(gh pr view "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --json title,labels) + TITLE=$(echo "$PR_DATA" | jq -r '.title') + HAS_LABEL=$(echo "$PR_DATA" | jq -r '.labels[]? | select(.name=="nightly-automerge") | .name' | head -1) + + # Match: "Release PR for X.Y.Z as nightly" + if ! [[ "$TITLE" =~ ^Release\ PR\ for\ [0-9]+\.[0-9]+\.[0-9]+\ as\ nightly$ ]]; then + echo "::error::PR title does not match release-PR sentinel: $TITLE" + exit 1 + fi + if [ -z "$HAS_LABEL" ]; then + echo "::error::PR is missing the 'nightly-automerge' label" + exit 1 + fi + echo "PR sentinel verified: $TITLE" + + - name: Install plugin-release-management + run: npm install -g @salesforce/plugin-release-management@latest --omit=dev + + - name: Run cli:release:automerge with retry + uses: salesforcecli/github-workflows/.github/actions/retry@npz/vscode-extension-ci + env: + GITHUB_TOKEN: ${{ secrets.bot-github-token }} + OWNER: ${{ github.repository_owner }} + REPO_NAME: ${{ github.event.repository.name }} + PR_NUMBER: ${{ inputs.pr-number }} + DRY_RUN_FLAG: ${{ inputs.dry-run == 'true' && '--dry-run' || '' }} + with: + max_attempts: ${{ inputs.max-attempts }} + retry_wait_seconds: ${{ inputs.retry-wait-seconds }} + retry_on: error + command: sf-release cli:release:automerge --owner "$OWNER" --repo "$REPO_NAME" --pull-number "$PR_NUMBER" --verbose $DRY_RUN_FLAG diff --git a/.github/workflows/vscode-ci-template.yml b/.github/workflows/vscode-ci-template.yml new file mode 100644 index 00000000..b52a0bab --- /dev/null +++ b/.github/workflows/vscode-ci-template.yml @@ -0,0 +1,215 @@ +name: CI + +# Reusable CI workflow template for VS Code extension repositories +# +# Usage from consuming repository: +# jobs: +# ci: +# uses: salesforcecli/github-workflows/.github/workflows/vscode/ci-template.yml@main +# with: +# lint-command: 'npm run lint' +# compile-command: 'npm run compile' +# test-command: 'npm run test' +# test-coverage-command: 'npm run test:coverage' +# +# Features: +# - Tests across multiple OS (Ubuntu, Windows) +# - Tests across Node.js versions (lts/-1, lts/*, current) +# - Coverage collection and reporting +# - Parallel test execution +# - Artifact upload for coverage reports + +on: + workflow_call: + inputs: + lint-command: + description: 'Command to run linting' + required: false + default: 'npm run lint' + type: string + compile-command: + description: 'Command to compile' + required: false + default: 'npm run compile' + type: string + test-command: + description: 'Command to run tests (without coverage)' + required: false + default: 'npm run test' + type: string + test-coverage-command: + description: 'Command to run tests with coverage' + required: false + default: 'npm run test:coverage' + type: string + coverage-report-command: + description: 'Command to merge coverage reports' + required: false + default: 'npm run test:coverage:report' + type: string + workflow_dispatch: + inputs: + lint-command: + description: 'Command to run linting' + required: false + default: 'npm run lint' + type: string + +# Add explicit permissions for security +permissions: + contents: read + pull-requests: read + actions: read + +jobs: + test: + name: Test + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + node-version: ['lts/-1', 'lts/*', 'current'] + fail-fast: false + + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + persist-credentials: false + submodules: false + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v6 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + + - name: Install dependencies + uses: ./.github/actions/vscode/npm-install-with-retries + + - name: Run linting + run: ${{ inputs.lint-command }} + + - name: Compile project + run: ${{ inputs.compile-command }} + + - name: Run tests with coverage (lts/current) + if: ${{ matrix.node-version != 'lts/-1' }} + run: ${{ inputs.test-coverage-command }} + + - name: Run tests (lts/-1, no coverage) + if: ${{ matrix.node-version == 'lts/-1' }} + env: + # Old-LTS defaults to ~4 GB old-space, which is too low for heavy stdlib suites. + # Keep this scoped to lts/-1 and non-coverage runs only. + NODE_OPTIONS: --max-old-space-size=6144 + run: ${{ inputs.test-command }} + + - name: Merge coverage reports + if: ${{ matrix.node-version != 'lts/-1' }} + run: ${{ inputs.coverage-report-command }} + + - name: Determine Node Label + id: node-label + shell: bash + env: + NODE_VERSION: ${{ matrix.node-version }} + run: | + if [ "$NODE_VERSION" = "lts/*" ]; then + echo "value=lts" >> $GITHUB_OUTPUT + elif [ "$NODE_VERSION" = "lts/-1" ]; then + echo "value=lts-1" >> $GITHUB_OUTPUT + elif [ "$NODE_VERSION" = "current" ]; then + echo "value=current" >> $GITHUB_OUTPUT + else + echo "value=$NODE_VERSION" >> $GITHUB_OUTPUT + fi + + - name: Upload coverage report + if: ${{ matrix.node-version != 'lts/-1' }} + uses: actions/upload-artifact@v7 + with: + name: coverage-report-${{ matrix.os }}-${{ steps.node-label.outputs.value }} + path: ./coverage + + test-quality: + name: Test Quality + needs: test + strategy: + matrix: + os: [ubuntu-latest] + node-version: ['lts/*'] + fail-fast: false + + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + persist-credentials: false + submodules: false + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v6 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + + - name: Install dependencies + uses: ./.github/actions/vscode/npm-install-with-retries + + - name: Run quality tests + run: npm run test:quality + + package: + name: Package + needs: test + if: ${{ needs.test.result == 'success' }} + uses: ./.github/workflows/vscode-package.yml + with: + branch: ${{ github.head_ref || github.ref_name }} + artifact-name: vsix-packages + dry-run: false + + ci-complete: + name: CI Complete + runs-on: ubuntu-latest + needs: [test, package] + if: always() + steps: + - name: Check all jobs result + env: + TEST_RESULT: ${{ needs.test.result }} + PACKAGE_RESULT: ${{ needs.package.result }} + run: | + if [[ "$TEST_RESULT" != "success" ]]; then + echo "Test job(s) failed" + exit 1 + fi + if [[ "$PACKAGE_RESULT" != "success" ]]; then + echo "Package job failed" + exit 1 + fi + echo "All jobs succeeded" + + slack-notify: + name: CI Failed Notification + needs: [test, package] + runs-on: ubuntu-latest + if: always() && github.event_name == 'push' && (needs.test.result == 'failure' || needs.package.result == 'failure') + steps: + - name: Notify Slack + uses: slackapi/slack-github-action@v3.0.3 + with: + payload: | + { + "text": "❌ CI Pipeline Failed", + "event": "CI workflow failed, run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}", + "repo": "${{ github.repository }}", + "test_result": "${{ needs.test.result }}", + "package_result": "${{ needs.package.result }}", + "branch": "${{ github.ref_name }}", + "commit": "${{ github.sha }}" + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.IDEE_MAIN_SLACK_WEBHOOK }} diff --git a/.github/workflows/vscode-draft-release-on-merge.yml b/.github/workflows/vscode-draft-release-on-merge.yml new file mode 100644 index 00000000..52e0bc56 --- /dev/null +++ b/.github/workflows/vscode-draft-release-on-merge.yml @@ -0,0 +1,262 @@ +name: Draft Release on Merge + +# Reusable workflow that fires when a "Release PR for as nightly" +# pull request is merged. It builds VSIXes via `vscode-package.yml` and +# publishes a GitHub *draft* release with the VSIXes attached. +# +# IMPORTANT — design invariant (SESSION-LOG section 3, section 8 #2): +# This workflow does NOT publish to the VS Code Marketplace. Nightlies are +# GitHub draft releases ONLY. Promotion to the Marketplace happens via the +# separate `vscode-promote-prerelease.yml` workflow on a longer cadence. +# +# Usage from consuming repository (pin to a version tag like @v1, not @main): +# on: +# pull_request: +# types: [closed] +# jobs: +# draft-release: +# if: | +# github.event.pull_request.merged == true && +# startsWith(github.event.pull_request.title, 'Release PR for') && +# endsWith(github.event.pull_request.title, 'as nightly') +# uses: salesforcecli/github-workflows/.github/workflows/vscode-draft-release-on-merge.yml@v1 +# with: +# extension-id: salesforce.salesforcedx-vscode +# vsix-glob: salesforcedx-vscode-*.vsix +# pr-title: ${{ github.event.pull_request.title }} +# secrets: +# bot-github-token: ${{ secrets. }} +# +# Required secret: +# bot-github-token (no default) — PAT used by `gh release create` to attach +# VSIXes and publish the draft release. Needs `repo` scope. + +on: + workflow_call: + inputs: + branch: + description: 'Branch the merged PR targeted (used as the build source)' + required: false + default: 'main' + type: string + extension-id: + description: | + Marketplace extension id (e.g. salesforce.salesforcedx-vscode). + Currently unused inside this workflow but accepted for forward-compatibility + and parity with vscode-publish-extensions.yml / vscode-make-pr-for-nightly.yml + callers. Will be wired up once vscode-package.yml supports per-extension globs (Phase 4). + required: true + type: string + vsix-glob: + description: | + VSIX filename glob. Single pattern (e.g. "salesforcedx-vscode-*.vsix") or JSON map. + Currently unused inside this workflow but accepted for forward-compatibility + and parity with vscode-publish-extensions.yml / vscode-make-pr-for-nightly.yml + callers. Will be wired up once vscode-package.yml supports per-extension globs (Phase 4). + required: true + type: string + tag-prefix: + description: 'Prefix used for marketplace tracking tags' + required: false + default: 'marketplace' + type: string + slack-name: + description: 'Display name used in Slack release notifications' + required: false + default: 'VS Code Extension' + type: string + pr-title: + description: 'The merged PR title, e.g. "Release PR for 65.9.0 as nightly". Used to extract the version + channel.' + required: true + type: string + dry-run: + description: 'Build VSIXes but do not create or update the GitHub draft release. Use for cross-repo testing.' + required: false + default: 'false' + type: string + secrets: + bot-github-token: + description: 'PAT used by `gh release create` to publish the draft release. See header docstring.' + required: true + slack-webhook: + description: 'Slack webhook URL for notifications. Optional; if omitted the slack step is skipped.' + required: false + +permissions: + contents: write + actions: read + +jobs: + parse-title: + name: Parse PR Title + runs-on: ubuntu-latest + outputs: + version: ${{ steps.parse.outputs.version }} + channel: ${{ steps.parse.outputs.channel }} + steps: + - name: Checkout (for local action) + uses: actions/checkout@v6 + + - name: Parse PR title + id: parse + uses: salesforcecli/github-workflows/.github/actions/vscode/parse-pr-title@npz/vscode-extension-ci + with: + title: ${{ inputs.pr-title }} + + - name: Validate channel is nightly + env: + CHANNEL: ${{ steps.parse.outputs.channel }} + run: | + set -euo pipefail + if [ "$CHANNEL" != "nightly" ]; then + echo "::error::This workflow only handles the nightly channel. Got channel='$CHANNEL'. Use vscode-promote-prerelease.yml for other channels." + exit 1 + fi + echo "Channel validated: $CHANNEL" + + build-vsixes: + name: Build VSIXes + needs: [parse-title] + uses: salesforcecli/github-workflows/.github/workflows/vscode-package.yml@npz/vscode-extension-ci + with: + branch: ${{ inputs.branch }} + pre-release: 'true' + artifact-name: vsix-packages-nightly-draft + + create-draft-release: + name: Create Draft Release + needs: [parse-title, build-vsixes] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ inputs.branch }} + + - name: Download VSIX artifacts + uses: actions/download-artifact@v8 + with: + name: ${{ needs.build-vsixes.outputs.artifact-name }} + path: ./vsix-artifacts + + - name: List downloaded VSIXes + run: | + set -euo pipefail + echo "Contents of ./vsix-artifacts:" + find ./vsix-artifacts -type f \( -name '*.vsix' -o -name '*.md5' -o -name '*.json' \) | sort + if [ -z "$(find ./vsix-artifacts -type f -name '*.vsix' -print -quit)" ]; then + echo "::error::No VSIX files found in artifacts." + exit 1 + fi + + - name: Create draft GitHub release (idempotent) + env: + GH_TOKEN: ${{ secrets.bot-github-token }} + VERSION: ${{ needs.parse-title.outputs.version }} + DRY_RUN: ${{ inputs.dry-run }} + run: | + set -euo pipefail + TAG="v${VERSION}" + TITLE="Nightly v${VERSION}" + NOTES="Nightly draft for testing. + + Install via: + gh release download ${TAG} --repo ${GITHUB_REPOSITORY} + code --install-extension + + This is a draft release — not published to the VS Code Marketplace." + + # Collect VSIX paths. + mapfile -t VSIX_FILES < <(find ./vsix-artifacts -type f -name '*.vsix' | sort) + echo "Found ${#VSIX_FILES[@]} VSIX file(s) to attach" + + if [ "$DRY_RUN" = "true" ]; then + echo "DRY RUN: would create or update draft release $TAG with these VSIXes:" + printf ' %s\n' "${VSIX_FILES[@]}" + exit 0 + fi + + # Idempotency / safety: + # - If release does not exist → create as draft (normal path). + # - If release exists AND is still a draft → upload assets with --clobber (re-run). + # - If release exists AND is published (isDraft=false) → fail loudly. A + # human likely promoted the draft via the GitHub UI; auto-uploading + # assets to a published release would silently corrupt a release the + # team has signed off on. + if RELEASE_JSON=$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json isDraft 2>/dev/null); then + IS_DRAFT=$(echo "$RELEASE_JSON" | jq -r '.isDraft') + if [ "$IS_DRAFT" = "true" ]; then + echo "Release $TAG already exists as draft — uploading VSIX assets with --clobber." + gh release upload "$TAG" "${VSIX_FILES[@]}" --repo "$GITHUB_REPOSITORY" --clobber + else + echo "::error::Release $TAG is already published (not a draft). Manual investigation required — refusing to upload assets to a published release." + exit 1 + fi + else + echo "Creating new draft release $TAG" + gh release create "$TAG" \ + --repo "$GITHUB_REPOSITORY" \ + --draft \ + --title "$TITLE" \ + --notes "$NOTES" \ + "${VSIX_FILES[@]}" + fi + echo "Draft release ready: $TAG" + + slack-notify: + name: Slack Notification + needs: [parse-title, create-draft-release] + runs-on: ubuntu-latest + if: always() && needs.create-draft-release.result == 'success' && inputs.dry-run != 'true' + # SLACK_WEBHOOK_URL must be at job level so the step `if:` can read it. + # Step-level `env:` is not materialized before the step's `if:` evaluates. + env: + SLACK_WEBHOOK_URL: ${{ secrets.slack-webhook }} + steps: + - name: Notify Slack + if: env.SLACK_WEBHOOK_URL != '' + uses: slackapi/slack-github-action@v3.0.3 + # Injection-safe payload: `slackapi/slack-github-action@v3` does NOT + # do bash-style `${VAR}` expansion inside `payload:`. Inputs/outputs + # are interpolated by GitHub Actions templating before the action runs. + # We wrap untrusted values (inputs.slack-name, outputs from prior jobs) + # in `toJSON(format(...))`, which produces a properly-quoted JSON string + # — embedded `"` and newlines are escaped — making JSON injection + # impossible regardless of input contents. Static GitHub-controlled + # fields (github.repository, github.run_id, github.server_url) are safe + # to inject directly without `toJSON`. + with: + payload: | + { + "text": ${{ toJSON(format('📦 {0} — Nightly draft release published', inputs.slack-name)) }}, + "blocks": [ + { + "type": "header", + "text": { + "type": "plain_text", + "text": ${{ toJSON(format('📦 {0} — Nightly Draft Release Published', inputs.slack-name)) }} + } + }, + { + "type": "section", + "fields": [ + { + "type": "mrkdwn", + "text": "*Repository:*\n${{ github.repository }}" + }, + { + "type": "mrkdwn", + "text": ${{ toJSON(format('*Version:*{0}v{1}', '\n', needs.parse-title.outputs.version)) }} + }, + { + "type": "mrkdwn", + "text": ${{ toJSON(format('*Release:*{0}<{1}/{2}/releases/tag/v{3}|View Release>', '\n', github.server_url, github.repository, needs.parse-title.outputs.version)) }} + }, + { + "type": "mrkdwn", + "text": "*Workflow Run:*\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Run>" + } + ] + } + ] + } diff --git a/.github/workflows/vscode-make-pr-for-nightly.yml b/.github/workflows/vscode-make-pr-for-nightly.yml new file mode 100644 index 00000000..a4e21c44 --- /dev/null +++ b/.github/workflows/vscode-make-pr-for-nightly.yml @@ -0,0 +1,311 @@ +name: Make PR for Nightly + +# Reusable workflow that creates a "Release PR for as nightly" pull +# request. The PR is labeled `nightly-automerge` so a separate consumer-side +# workflow (which calls vscode-automerge-nightly-pr.yml) can merge it once +# checks pass. +# +# Usage from consuming repository (pin to a version tag like @v1, not @main): +# jobs: +# make-nightly-pr: +# uses: salesforcecli/github-workflows/.github/workflows/vscode-make-pr-for-nightly.yml@v1 +# with: +# extension-id: salesforce.salesforcedx-vscode +# vsix-glob: salesforcedx-vscode-*.vsix +# bot-user: +# secrets: +# bot-github-token: ${{ secrets. }} +# +# Requirements: +# - Consuming repo must have @salesforce/vscode-extension-ci installed as devDependency +# (so `node node_modules/github-workflows/packages/vscode-extension-ci/dist/cli.js` +# resolves at runtime). Mirrors the requirement of `vscode-publish-extensions.yml`. +# +# Required secret: +# bot-github-token (no default) — Personal Access Token (PAT) for the bot +# account identified by `bot-user`. The PAT must: +# - own the same identity as `bot-user` (so commits/PRs are authored by it) +# - have `repo` and `workflow` scopes +# - be able to push to `inputs.branch` (typically `main` requires admin +# or specific branch-protection bypass for the bot) + +on: + workflow_call: + inputs: + branch: + description: 'Branch to read main version from and open the PR against' + required: false + default: 'main' + type: string + extension-id: + description: 'Marketplace extension id (e.g. salesforce.salesforcedx-vscode) used for marketplace lookup' + required: true + type: string + vsix-glob: + description: 'VSIX filename glob. Single pattern (e.g. "salesforcedx-vscode-*.vsix") or JSON map of extension name to pattern. Passed through for forward-compat with downstream steps.' + required: true + type: string + tag-prefix: + description: 'Prefix used for marketplace tracking tags' + required: false + default: 'marketplace' + type: string + bot-user: + description: 'GitHub username of the bot account that creates the PR. No default — consumer must be explicit.' + required: true + type: string + new-major: + description: 'Manual override for major version (whole number). Leave empty to use auto-bump.' + required: false + type: string + force-new-major: + description: 'Set to "true" to bypass new-major +1 validation. Use with care.' + required: false + default: 'false' + type: string + slack-name: + description: 'Display name used in Slack release notifications' + required: false + default: 'VS Code Extension' + type: string + dry-run: + description: 'Run without pushing the branch or opening the PR. Bumper still runs and version is computed. The pr-url output is set to "(dry-run, no PR created)" sentinel; pr-branch and new-version outputs are populated as in a real run. Use for cross-repo testing.' + required: false + default: 'false' + type: string + secrets: + bot-github-token: + description: 'PAT for bot-user. Needs `repo` + `workflow` scopes. See header docstring.' + required: true + slack-webhook: + description: 'Slack webhook URL for notifications. Optional; if omitted the slack step is skipped.' + required: false + +permissions: + contents: write + pull-requests: write + +jobs: + create_pull_request: + name: Create Nightly Release PR + runs-on: ubuntu-latest + outputs: + new-version: ${{ steps.bump.outputs.new-version }} + pr-url: ${{ steps.create-pr.outputs.pr-url }} + pr-branch: ${{ steps.create-pr.outputs.pr-branch }} + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ inputs.branch }} + fetch-depth: 0 + token: ${{ secrets.bot-github-token }} + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '22.x' + + - name: Install dependencies + uses: salesforcecli/github-workflows/.github/actions/vscode/npm-install-with-retries@npz/vscode-extension-ci + + - name: Run version bumper + id: bump + env: + EXTENSION_ID: ${{ inputs.extension-id }} + VSIX_GLOB: ${{ inputs.vsix-glob }} + TAG_PREFIX: ${{ inputs.tag-prefix }} + IS_NIGHTLY: 'true' + PRE_RELEASE: 'true' + IS_PROMOTION: 'false' + SELECTED_EXTENSIONS: 'all' + NEW_MAJOR: ${{ inputs.new-major }} + FORCE_NEW_MAJOR: ${{ inputs.force-new-major }} + GITHUB_TOKEN: ${{ secrets.bot-github-token }} + BRANCH: ${{ inputs.branch }} + run: | + set -euo pipefail + node node_modules/github-workflows/packages/vscode-extension-ci/dist/cli.js ext-version-bumper + + # Determine the new version. Rather than reading from `packages/*/package.json` + # (which is fragile if non-extension packages exist in `packages/`), we ask + # git which `package.json` files were modified by the bumper, parse `version` + # from each, and assert they all agree (same-semver invariant — see + # SESSION-LOG.md §8 #1). + NEW_VERSION="" + # Use git to find package.json files that were actually modified. + # `git diff HEAD` covers both staged and unstaged changes — works whether + # the bumper auto-stages its edits or leaves them in the working tree. + MODIFIED_PJS=$(git diff HEAD --name-only -- '**/package.json' 'package.json') + if [ -z "$MODIFIED_PJS" ]; then + echo "::error::Version bumper did not modify any package.json files." + exit 1 + fi + while IFS= read -r pj; do + [ -z "$pj" ] && continue + [ ! -f "$pj" ] && continue + # Let parse failures crash the step. The bumper succeeded earlier, so a + # malformed package.json now indicates a real problem worth failing on. + V=$(node -p "require('./$pj').version") + if [ -z "$NEW_VERSION" ]; then + NEW_VERSION="$V" + elif [ "$V" != "$NEW_VERSION" ]; then + echo "::error::Inconsistent versions across modified package.json files. Got '$V' in $pj but expected '$NEW_VERSION'. The same-semver invariant requires all extensions in the pack to share a version (see SESSION-LOG.md §8 #1)." + exit 1 + fi + done <<< "$MODIFIED_PJS" + if [ -z "$NEW_VERSION" ]; then + echo "::error::Could not determine new version from modified package.json files." + exit 1 + fi + echo "Resolved new version: $NEW_VERSION" + echo "new-version=$NEW_VERSION" >> "$GITHUB_OUTPUT" + + - name: Configure git identity for bot + env: + BOT_USER: ${{ inputs.bot-user }} + run: | + set -euo pipefail + # Bot accounts hide their email by default, so `gh api user --jq .email` + # returns null. Use the noreply email pattern directly — that's what + # GitHub recommends for bot-authored commits. + git config --local user.name "$BOT_USER" + git config --local user.email "${BOT_USER}@users.noreply.github.com" + echo "Configured git identity: $BOT_USER <${BOT_USER}@users.noreply.github.com>" + + - name: Commit and push release branch + id: create-pr + env: + GH_TOKEN: ${{ secrets.bot-github-token }} + NEW_VERSION: ${{ steps.bump.outputs.new-version }} + BASE_BRANCH: ${{ inputs.branch }} + DRY_RUN: ${{ inputs.dry-run }} + run: | + set -euo pipefail + + PR_BRANCH="nightly/v${NEW_VERSION}" + echo "Release branch: $PR_BRANCH" + + # Use bot token for push. + git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" + + # Re-run guard: this workflow always pushes a fresh branch. If the branch + # already exists on the remote, a previous run partially failed — bail out + # and require manual cleanup rather than silently overwriting. + # In dry-run mode we still run the guard so we surface stale branches early. + if git ls-remote --heads origin "$PR_BRANCH" | grep -q "$PR_BRANCH"; then + echo "::error::Branch '$PR_BRANCH' already exists on origin. A previous run likely failed mid-way. Manually delete the stale branch (git push origin --delete '$PR_BRANCH') and close any open PR before re-running." + exit 1 + fi + + git checkout -b "$PR_BRANCH" + git add -A + # Skipping `git diff --staged --quiet` guard here: the version-bumper + # step above already asserted that the bumper produced edits to one or + # more package.json files, so this guard would never trigger. + git commit -m "chore: release ${NEW_VERSION} as nightly [skip ci]" + + PR_TITLE="Release PR for ${NEW_VERSION} as nightly" + + if [ "$DRY_RUN" = "true" ]; then + # Note: the bumper edits, `git checkout -b`, and `git commit` above + # all ran — they only mutate the runner workspace. We never push, + # and the workspace is discarded when the job ends. Safe. + echo "DRY RUN: would push branch $PR_BRANCH" + echo "DRY RUN: would open PR titled '$PR_TITLE' against base '$BASE_BRANCH' with label 'nightly-automerge'" + echo "pr-url=(dry-run, no PR created)" >> "$GITHUB_OUTPUT" + echo "pr-branch=$PR_BRANCH" >> "$GITHUB_OUTPUT" + exit 0 + fi + + # Plain push: branch should not exist yet (we just guarded above). If a + # concurrent re-run wins the race, plain push will fail loudly rather than + # force-clobbering — preferred behavior. + git push origin "$PR_BRANCH" + + # Build the PR body via printf to a file. Avoids heredoc-indentation + # pitfalls (a YAML-indented EOF marker won't terminate the heredoc). + PR_BODY_FILE="$(mktemp)" + printf '%s\n' \ + "Auto-generated nightly release PR." \ + "" \ + "- Version: \`${NEW_VERSION}\`" \ + "- Channel: \`nightly\`" \ + "- Source branch: \`${BASE_BRANCH}\`" \ + "" \ + "When checks pass, the \`nightly-automerge\` label triggers the" \ + "automerge workflow. After merge, the draft-release workflow builds" \ + "VSIXes and publishes a GitHub draft release for testing." \ + "" \ + "Do not edit this PR by hand." \ + > "$PR_BODY_FILE" + + PR_URL=$(gh pr create \ + --repo "$GITHUB_REPOSITORY" \ + --base "$BASE_BRANCH" \ + --head "$PR_BRANCH" \ + --title "$PR_TITLE" \ + --body-file "$PR_BODY_FILE" \ + --label "nightly-automerge") + + echo "Created PR: $PR_URL" + echo "pr-url=$PR_URL" >> "$GITHUB_OUTPUT" + echo "pr-branch=$PR_BRANCH" >> "$GITHUB_OUTPUT" + + slack_notification: + name: Slack Notification + needs: [create_pull_request] + runs-on: ubuntu-latest + if: always() && needs.create_pull_request.result == 'success' && inputs.dry-run != 'true' + # SLACK_WEBHOOK_URL must be at job level so the step `if:` can read it. + # Step-level `env:` is not materialized before the step's `if:` evaluates. + env: + SLACK_WEBHOOK_URL: ${{ secrets.slack-webhook }} + steps: + - name: Notify Slack + if: env.SLACK_WEBHOOK_URL != '' + uses: slackapi/slack-github-action@v3.0.3 + # Injection-safe payload: `slackapi/slack-github-action@v3` does NOT + # do bash-style `${VAR}` expansion inside `payload:`. Inputs/outputs + # are interpolated by GitHub Actions templating before the action runs. + # We wrap untrusted values (inputs.slack-name, outputs from prior jobs) + # in `toJSON(format(...))`, which produces a properly-quoted JSON string + # — embedded `"` and newlines are escaped — making JSON injection + # impossible regardless of input contents. Static GitHub-controlled + # fields (github.repository, github.run_id, github.server_url) are safe + # to inject directly without `toJSON`. + with: + payload: | + { + "text": ${{ toJSON(format('📬 {0} — Nightly release PR opened', inputs.slack-name)) }}, + "blocks": [ + { + "type": "header", + "text": { + "type": "plain_text", + "text": ${{ toJSON(format('📬 {0} — Nightly Release PR Opened', inputs.slack-name)) }} + } + }, + { + "type": "section", + "fields": [ + { + "type": "mrkdwn", + "text": "*Repository:*\n${{ github.repository }}" + }, + { + "type": "mrkdwn", + "text": ${{ toJSON(format('*Version:*{0}{1}', '\n', needs.create_pull_request.outputs.new-version)) }} + }, + { + "type": "mrkdwn", + "text": ${{ toJSON(format('*PR:*{0}<{1}|View PR>', '\n', needs.create_pull_request.outputs.pr-url)) }} + }, + { + "type": "mrkdwn", + "text": "*Workflow Run:*\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Run>" + } + ] + } + ] + } diff --git a/.github/workflows/vscode-package.yml b/.github/workflows/vscode-package.yml new file mode 100644 index 00000000..81166272 --- /dev/null +++ b/.github/workflows/vscode-package.yml @@ -0,0 +1,223 @@ +name: Package + +# Reusable workflow for packaging VS Code extensions into VSIX files +# +# Usage from consuming repository: +# jobs: +# package: +# uses: salesforcecli/github-workflows/.github/workflows/vscode/package.yml@main +# with: +# branch: main +# pre-release: true + +on: + workflow_call: + inputs: + node-version: + description: 'Node.js version to use' + required: false + default: '22.x' + type: string + branch: + description: 'Branch to package from' + required: false + default: 'main' + type: string + artifact-name: + description: 'Name for the VSIX artifacts (base name or pre-calculated: vsix-packages-{run_number}-{mode})' + required: false + default: 'vsix-packages' + type: string + dry-run: + description: 'Run in dry-run mode' + required: false + default: 'false' + type: string + pre-release: + description: 'Indicates if this is a pre-release version' + required: false + default: 'false' + type: string + outputs: + artifact-name: + description: 'The calculated artifact name' + value: ${{ jobs.package.outputs.artifact-name }} + workflow_dispatch: + inputs: + node-version: + description: 'Node.js version to use' + required: false + default: '22.x' + type: string + branch: + description: 'Branch to package from' + required: false + default: 'main' + type: string + dry-run: + description: 'Run in dry-run mode' + required: false + default: 'false' + type: string + pre-release: + description: 'Indicates if this is a pre-release version' + required: false + default: 'false' + type: string + +# Add explicit permissions for security +permissions: + contents: read + actions: read + +jobs: + package: + name: Package + runs-on: ubuntu-latest + outputs: + artifact-name: ${{ steps.calc-artifact-name.outputs.artifact-name }} + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + ref: ${{ inputs.branch || github.head_ref || github.ref }} + + - name: Setup Node.js ${{ inputs.node-version || '22.x' }} + uses: actions/setup-node@v6 + with: + node-version: ${{ inputs.node-version || '22.x' }} + + - name: Install dependencies + uses: ./.github/actions/vscode/npm-install-with-retries + + # Consumer repo defines `npm run package:packages` (and optionally `:prerelease`) + # which produces VSIX files under packages/**. + - name: Package packages + run: | + if [ "${{ inputs.pre-release }}" = "true" ]; then + npm run package:packages:prerelease + else + npm run package:packages + fi + + - name: Generate MD5 checksums + id: md5-checksums + run: | + echo "Generating MD5 checksums for VSIX files..." + + # All VSIX files produced by the consumer repo's package script(s) + VSIX_FILES=$(find packages -name "*.vsix" -type f) + + if [ -z "$VSIX_FILES" ]; then + echo "No VSIX files found to generate checksums for" + echo "checksums_generated=false" >> $GITHUB_OUTPUT + exit 0 + fi + + # Create checksums directory structure + CHECKSUMS_FILE="checksums.md5" + CHECKSUMS_JSON_FILE="checksums.json" + > "$CHECKSUMS_FILE" # Create/clear checksums file + > "$CHECKSUMS_JSON_FILE" # Create/clear JSON file + echo "[" > "$CHECKSUMS_JSON_FILE" + + FIRST=true + # Generate MD5 checksums for each VSIX file + while IFS= read -r vsix_file; do + if [ -f "$vsix_file" ]; then + # Generate MD5 checksum + MD5_HASH=$(md5sum "$vsix_file" | cut -d' ' -f1) + + # Get relative path for display + RELATIVE_PATH=$(echo "$vsix_file" | sed 's|^packages/||') + + # Get file size + FILE_SIZE=$(stat -c%s "$vsix_file" 2>/dev/null || stat -f%z "$vsix_file" 2>/dev/null || echo "0") + + # Create individual .md5 file alongside VSIX file + MD5_FILE="${vsix_file}.md5" + echo "$MD5_HASH $(basename "$vsix_file")" > "$MD5_FILE" + + # Add to combined checksums file + echo "$MD5_HASH $RELATIVE_PATH" >> "$CHECKSUMS_FILE" + + # Add to JSON file for workflow summary + if [ "$FIRST" = true ]; then + FIRST=false + else + echo "," >> "$CHECKSUMS_JSON_FILE" + fi + echo " {\"file\":\"$RELATIVE_PATH\",\"md5\":\"$MD5_HASH\",\"size\":\"$FILE_SIZE\"}" >> "$CHECKSUMS_JSON_FILE" + + echo "Generated MD5 for: $RELATIVE_PATH" + echo " MD5: $MD5_HASH" + echo " Size: $FILE_SIZE bytes" + fi + done <<< "$VSIX_FILES" + + echo "]" >> "$CHECKSUMS_JSON_FILE" + + # Move combined checksums files to packages root for artifact upload + mv "$CHECKSUMS_FILE" "packages/checksums.md5" + mv "$CHECKSUMS_JSON_FILE" "packages/checksums.json" + + echo "checksums_generated=true" >> $GITHUB_OUTPUT + echo "checksums_file=packages/checksums.json" >> $GITHUB_OUTPUT + + - name: Calculate artifact name + id: calc-artifact-name + uses: ./.github/actions/vscode/calculate-artifact-name + with: + artifact-name: ${{ inputs.artifact-name }} + dry-run: ${{ inputs.dry-run }} + + - name: Upload VSIX artifacts + id: upload + uses: actions/upload-artifact@v7 + with: + name: ${{ steps.calc-artifact-name.outputs.artifact-name }} + path: | + packages/**/*.vsix + packages/**/*.vsix.md5 + packages/checksums.md5 + packages/checksums.json + retention-days: 5 + + - name: List VSIX files + run: | + echo "VSIX files created:" + find packages -name "*.vsix" -exec ls -la {} \; + + - name: Add MD5 checksums to workflow summary + if: steps.md5-checksums.outputs.checksums_generated == 'true' + run: | + echo "## MD5 Checksums" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "MD5 checksums for all VSIX extension files:" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Extension | MD5 Checksum | Size |" >> $GITHUB_STEP_SUMMARY + echo "|-----------|-------------|------|" >> $GITHUB_STEP_SUMMARY + + # Read checksums from JSON file and format table + CHECKSUMS_FILE="${{ steps.md5-checksums.outputs.checksums_file }}" + + if [ -f "$CHECKSUMS_FILE" ]; then + # Use node to parse JSON and format table + node -e " + const fs = require('fs'); + const checksums = JSON.parse(fs.readFileSync('$CHECKSUMS_FILE', 'utf8')); + checksums.forEach(item => { + const file = item.file || 'unknown'; + const md5 = item.md5 || 'unknown'; + const size = item.size || '0'; + const sizeFormatted = size !== '0' && size !== 'unknown' ? (parseInt(size) / 1024).toFixed(2) + ' KB' : 'unknown'; + console.log(\`|\${file} |\` + md5 + \` |\${sizeFormatted}|\`); + }); + " >> $GITHUB_STEP_SUMMARY + else + echo "| No checksums available | - | - |" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Note:** Individual \`.md5\` files are available alongside each VSIX file in the artifacts." >> $GITHUB_STEP_SUMMARY + echo "A combined \`checksums.md5\` file and \`checksums.json\` file are also included in the artifacts." >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/vscode-promote-prerelease.yml b/.github/workflows/vscode-promote-prerelease.yml new file mode 100644 index 00000000..5031d631 --- /dev/null +++ b/.github/workflows/vscode-promote-prerelease.yml @@ -0,0 +1,238 @@ +name: Promote Nightly to Pre-release + +# Reusable workflow for promoting nightly builds to pre-release on marketplace +# +# Usage from consuming repository: +# jobs: +# promote: +# uses: salesforcecli/github-workflows/.github/workflows/vscode/promote-prerelease.yml@main +# with: +# min-tag-age-days: '7' +# secrets: inherit +# +# Requirements: +# - Consuming repo must have @salesforce/vscode-extension-ci installed as devDependency +# - Requires IDEE_GH_TOKEN, VSCE_PERSONAL_ACCESS_TOKEN, IDEE_OVSX_PAT secrets + +on: + workflow_call: + inputs: + min-tag-age-days: + description: 'Minimum nightly age in days before eligible for promotion' + required: false + default: '7' + type: string + dry-run: + description: 'Run in dry-run mode (no actual publishing or tagging)' + required: false + default: 'false' + type: string + extension-id: + description: 'Marketplace extension id (e.g. salesforce.salesforcedx-vscode)' + required: true + type: string + vsix-glob: + description: 'VSIX filename glob. Either a single pattern applied to all extensions (e.g. "salesforcedx-vscode-*.vsix") or a JSON map of extension name to pattern (e.g. ''{"core":"...","apex":"..."}'').' + required: true + type: string + tag-prefix: + description: 'Prefix used for marketplace tracking tags' + required: false + default: 'marketplace' + type: string + slack-name: + description: 'Display name used in Slack release notifications' + required: false + default: 'VS Code Extension' + type: string + workflow_dispatch: + inputs: + min-tag-age-days: + description: 'Minimum nightly age in days before eligible for promotion (default: 7)' + required: false + default: '7' + type: string + dry-run: + description: 'Run in dry-run mode (no actual publishing or tagging)' + required: false + default: 'false' + type: choice + options: + - 'false' + - 'true' + extension-id: + description: 'Marketplace extension id (e.g. salesforce.salesforcedx-vscode)' + required: true + type: string + vsix-glob: + description: 'VSIX filename glob. Either a single pattern applied to all extensions (e.g. "salesforcedx-vscode-*.vsix") or a JSON map of extension name to pattern (e.g. ''{"core":"...","apex":"..."}'').' + required: true + type: string + tag-prefix: + description: 'Prefix used for marketplace tracking tags' + required: false + default: 'marketplace' + type: string + slack-name: + description: 'Display name used in Slack release notifications' + required: false + default: 'VS Code Extension' + type: string + +concurrency: + group: promote-prerelease + cancel-in-progress: false + +permissions: + contents: write + packages: write + actions: read + +jobs: + find-nightly-candidate: + runs-on: ubuntu-latest + outputs: + commit-sha: ${{ steps.find.outputs.commit-sha }} + nightly-tag: ${{ steps.find.outputs.nightly-tag }} + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + token: ${{ secrets.IDEE_GH_TOKEN }} + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '22.x' + + - name: Install dependencies + uses: ./.github/actions/vscode/npm-install-with-retries + + - name: Find eligible nightly + id: find + env: + MIN_TAG_AGE_DAYS: ${{ inputs.min-tag-age-days || '7' }} + EXTENSION_ID: ${{ inputs.extension-id }} + VSIX_GLOB: ${{ inputs.vsix-glob }} + TAG_PREFIX: ${{ inputs.tag-prefix }} + run: | + node node_modules/github-workflows/packages/vscode-extension-ci/dist/cli.js ext-nightly-finder + + - name: Fail if no candidate found + if: steps.find.outputs.nightly-tag == '' + run: | + echo "No eligible nightly candidate found. Nothing to promote this week." + exit 1 + + quality-gate: + needs: find-nightly-candidate + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Check CI status for candidate commit + uses: ./.github/actions/vscode/check-ci-status + with: + commit-sha: ${{ needs.find-nightly-candidate.outputs.commit-sha }} + token: ${{ secrets.IDEE_GH_TOKEN }} + + publish: + needs: [find-nightly-candidate, quality-gate] + runs-on: ubuntu-latest + strategy: + matrix: + include: + - registry: vsce + publish-tool: vsce + - registry: ovsx + publish-tool: ovsx + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + token: ${{ secrets.IDEE_GH_TOKEN }} + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '22.x' + + - name: Install dependencies + uses: ./.github/actions/vscode/npm-install-with-retries + + - name: Download VSIX from nightly GitHub release + env: + GH_TOKEN: ${{ secrets.IDEE_GH_TOKEN }} + NIGHTLY_TAG: ${{ needs.find-nightly-candidate.outputs.nightly-tag }} + run: | + mkdir -p ./vsix-artifacts + echo "Downloading VSIX from release: $NIGHTLY_TAG" + gh release download "$NIGHTLY_TAG" \ + --pattern "*.vsix" \ + --dir ./vsix-artifacts \ + --repo "${{ github.repository }}" + + VSIX_FILE=$(find ./vsix-artifacts -type f -name "${{ inputs.vsix-glob }}" | head -1) + if [ -z "$VSIX_FILE" ]; then + echo "No VSIX found in release $NIGHTLY_TAG" + exit 1 + fi + echo "Found VSIX: $VSIX_FILE" + echo "VSIX_PATH=$VSIX_FILE" >> $GITHUB_ENV + + - name: Publish to ${{ matrix.registry }} + uses: ./.github/actions/vscode/publish-vsix + with: + vsix-path: ${{ env.VSIX_PATH }} + publish-tool: ${{ matrix.publish-tool }} + pre-release: 'true' + dry-run: ${{ inputs.dry-run || 'false' }} + env: + VSCE_PERSONAL_ACCESS_TOKEN: ${{ secrets.VSCE_PERSONAL_ACCESS_TOKEN }} + OVSX_PAT: ${{ secrets.IDEE_OVSX_PAT }} + + tag-promoted: + needs: [find-nightly-candidate, publish] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + token: ${{ secrets.IDEE_GH_TOKEN }} + + - name: Create marketplace-prerelease tracking tag + env: + NIGHTLY_TAG: ${{ needs.find-nightly-candidate.outputs.nightly-tag }} + DRY_RUN: ${{ inputs.dry-run || 'false' }} + GH_TOKEN: ${{ secrets.IDEE_GH_TOKEN }} + run: | + # Extract version from the nightly tag (format: ...-v-nightly.*) + VERSION=$(echo "$NIGHTLY_TAG" | grep -oP '\d+\.\d+\.\d+' | head -1) + if [ -z "$VERSION" ]; then + echo "Could not extract version from tag: $NIGHTLY_TAG" + exit 1 + fi + + TRACKING_TAG="${{ inputs.tag-prefix }}-prerelease-${{ inputs.extension-id }}-v${VERSION}" + COMMIT_SHA="${{ needs.find-nightly-candidate.outputs.commit-sha }}" + + echo "Creating tracking tag: $TRACKING_TAG → $COMMIT_SHA" + + if [ "$DRY_RUN" = "true" ]; then + echo "DRY RUN: Would create and push tag $TRACKING_TAG" + else + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git remote set-url origin https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git + git fetch --tags origin + if git tag --list "$TRACKING_TAG" | grep -q .; then + echo "⏭️ Tracking tag $TRACKING_TAG already exists — skipping (idempotent rerun)" + else + git tag "$TRACKING_TAG" "$COMMIT_SHA" + git push origin "$TRACKING_TAG" + echo "Tracking tag pushed: $TRACKING_TAG" + fi + fi diff --git a/.github/workflows/vscode-publish-extensions.yml b/.github/workflows/vscode-publish-extensions.yml new file mode 100644 index 00000000..181f8529 --- /dev/null +++ b/.github/workflows/vscode-publish-extensions.yml @@ -0,0 +1,785 @@ +name: Publish VS Code Extensions + +# Reusable workflow for building, versioning, and publishing VS Code extensions +# +# Usage from consuming repository (pin to a version tag like @v1, not @main): +# jobs: +# publish: +# uses: salesforcecli/github-workflows/.github/workflows/vscode-publish-extensions.yml@v1 +# with: +# extension-id: salesforce.salesforcedx-vscode # required: Marketplace id +# vsix-glob: salesforcedx-vscode-*.vsix # required: VSIX filename glob, single pattern (all extensions) +# # or per-extension JSON map: vsix-glob: '{"core":"salesforcedx-vscode-core-*.vsix","apex":"salesforcedx-vscode-apex-*.vsix"}' +# extensions: changed # or 'all' or specific extension names +# registries: all # or 'vsce' or 'ovsx' +# pre-release: true # true for nightly/pre-release, false for stable +# dry-run: false +# secrets: inherit +# +# Requirements: +# - Consuming repo must have @salesforce/vscode-extension-ci installed as devDependency +# - Requires secrets: IDEE_GH_TOKEN, VSCE_PERSONAL_ACCESS_TOKEN, IDEE_OVSX_PAT +# +# Features: +# - Auto-detects changed extensions +# - Marketplace-lookup version selection +# - Conventional commit analysis +# - GitHub release creation with VSIX artifacts +# - Marketplace publishing (can be skipped for nightly by setting registries appropriately) + +on: + workflow_call: + inputs: + branch: + description: 'Branch to release from' + required: false + default: 'main' + type: string + extensions: + description: 'Extensions to release (all, changed, or comma-separated extension names)' + required: false + default: 'changed' + type: string + registries: + description: 'Registries to publish to (all, vsce, ovsx)' + required: false + default: 'all' + type: string + available-extensions: + description: 'Available VS Code extensions' + required: false + type: string + dry-run: + description: 'Run in dry-run mode (no actual publishing)' + required: false + default: 'false' + type: string + pre-release: + description: 'Publish as pre-release version' + required: false + default: 'true' + type: string + + version-bump: + description: 'Version bump type (auto, patch, minor, major)' + required: false + default: 'auto' + type: string + extension-id: + description: 'Marketplace extension id (e.g. salesforce.salesforcedx-vscode)' + required: true + type: string + vsix-glob: + description: 'VSIX filename glob. Either a single pattern applied to all extensions (e.g. "salesforcedx-vscode-*.vsix") or a JSON map of extension name to pattern (e.g. ''{"core":"...","apex":"..."}'').' + required: true + type: string + tag-prefix: + description: 'Prefix used for marketplace tracking tags' + required: false + default: 'marketplace' + type: string + slack-name: + description: 'Display name used in Slack release notifications' + required: false + default: 'VS Code Extension' + type: string + is-nightly: + description: 'Whether this run is a nightly build (boolean as string for env-var compatibility)' + required: false + default: 'false' + type: string + +# Add explicit permissions for security +permissions: + contents: write # Needed for version bumps and releases + packages: write # Needed for publishing to registries + actions: read + +jobs: + determine-changes: + runs-on: ubuntu-latest + outputs: + selected-extensions: ${{ steps.changes.outputs.selected-extensions }} + version-bumps: ${{ steps.changes.outputs.version-bumps }} + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '22.x' + + - name: Install dependencies + uses: ./.github/actions/vscode/npm-install-with-retries + + - name: Determine changes and version bumps + id: changes + env: + IS_NIGHTLY: ${{ inputs.is-nightly }} + VERSION_BUMP: 'auto' + PRE_RELEASE: ${{ inputs.pre-release || 'true' }} + IS_PROMOTION: 'false' + SELECTED_EXTENSIONS: ${{ inputs.extensions }} + EXTENSION_ID: ${{ inputs.extension-id }} + VSIX_GLOB: ${{ inputs.vsix-glob }} + TAG_PREFIX: ${{ inputs.tag-prefix }} + run: | + node node_modules/github-workflows/packages/vscode-extension-ci/dist/cli.js ext-change-detector + + display-release-plan: + needs: [determine-changes] + runs-on: ubuntu-latest + if: needs.determine-changes.outputs.selected-extensions != '' + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ inputs.branch || github.ref }} + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '22.x' + + - name: Install dependencies + uses: ./.github/actions/vscode/npm-install-with-retries + + - name: Display Extension Release Plan + env: + BRANCH: ${{ inputs.branch || github.ref_name }} + BUILD_TYPE: ${{ github.event_name }} + IS_NIGHTLY: ${{ inputs.is-nightly }} + VERSION_BUMP: ${{ needs.determine-changes.outputs.version-bumps }} + REGISTRIES: ${{ inputs.registries }} + PRE_RELEASE: ${{ inputs.pre-release || 'true' }} + SELECTED_EXTENSIONS: ${{ needs.determine-changes.outputs.selected-extensions }} + EXTENSION_ID: ${{ inputs.extension-id }} + VSIX_GLOB: ${{ inputs.vsix-glob }} + TAG_PREFIX: ${{ inputs.tag-prefix }} + run: | + node node_modules/github-workflows/packages/vscode-extension-ci/dist/cli.js ext-release-plan + + bump-versions: + needs: [determine-changes] + runs-on: ubuntu-latest + if: needs.determine-changes.outputs.selected-extensions != '' + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + token: ${{ secrets.IDEE_GH_TOKEN }} + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '22.x' + + - name: Install dependencies + uses: ./.github/actions/vscode/npm-install-with-retries + + - name: Bump versions and tag for selected extensions + env: + VERSION_BUMP: ${{ needs.determine-changes.outputs.version-bumps }} + SELECTED_EXTENSIONS: ${{ needs.determine-changes.outputs.selected-extensions }} + PRE_RELEASE: ${{ inputs.pre-release || github.event.inputs.pre-release || 'false' }} + IS_NIGHTLY: ${{ inputs.is-nightly }} + IS_PROMOTION: 'false' + EXTENSION_ID: ${{ inputs.extension-id }} + VSIX_GLOB: ${{ inputs.vsix-glob }} + TAG_PREFIX: ${{ inputs.tag-prefix }} + run: | + node node_modules/github-workflows/packages/vscode-extension-ci/dist/cli.js ext-version-bumper + + - name: Validate GitHub authentication + env: + GITHUB_TOKEN: ${{ secrets.IDEE_GH_TOKEN }} + run: | + # Validate that required tokens are present + if [ -z "$GITHUB_TOKEN" ]; then + echo "❌ Error: GITHUB_TOKEN is not set" + exit 1 + fi + + # Test GitHub CLI authentication + if ! gh auth status >/dev/null 2>&1; then + echo "❌ Error: GitHub CLI authentication failed" + exit 1 + fi + + echo "✅ GitHub authentication validated" + + # If the branch push succeeds but tag push fails, do NOT re-run this job. + # Manually push the missing tags: git push origin --tags + - name: Commit version bumps with tags + env: + # Ensure GitHub CLI has proper authentication + GITHUB_TOKEN: ${{ secrets.IDEE_GH_TOKEN }} + DRY_RUN: ${{ inputs.dry-run || github.event.inputs.dry-run || 'false' }} + run: | + if [ "$DRY_RUN" = "true" ]; then + echo "🔄 DRY RUN: Would commit and push version bumps..." + echo "📋 DRY RUN: Changes that would be committed:" + git status --porcelain + echo "📋 DRY RUN: Tags that would be pushed:" + git tag --list | tail -10 || echo "No tags found" + echo "✅ DRY RUN: Would commit version bumps and push tags" + else + echo "🔄 Committing version bumps..." + + # Configure git for the action + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + + # Configure git to use the PAT for authentication + git remote set-url origin https://x-access-token:${{ secrets.IDEE_GH_TOKEN }}@github.com/${{ github.repository }}.git + + # Add all changes + # Note: git add . respects .gitignore, so ignored files won't be added + # This is intentional - Wireit output files in test fixtures should remain ignored + git add . + + if git diff --staged --quiet; then + # Nothing to stage — check whether the bump was already committed to remote + # (idempotent rerun: version bumper ran, committed, pushed, then a later step failed) + git fetch origin ${{ inputs.branch || github.ref_name }} + REMOTE_MSG=$(git log -1 --format='%s' origin/${{ inputs.branch || github.ref_name }}) + if echo "$REMOTE_MSG" | grep -q "chore: bump versions for release"; then + echo "⏭️ Version bump already committed to remote — skipping commit (idempotent rerun)" + else + echo "❌ Error: No staged changes and no prior bump commit found on remote. Version bumper may have failed silently." + exit 1 + fi + else + # Create commit with version bump message + git commit -m "chore: bump versions for release [skip ci]" + + # Push version bumps — retry once with rebase on non-fast-forward + echo "Pushing version bumps to ${{ inputs.branch || github.ref_name }}..." + if ! git push origin HEAD:${{ inputs.branch || github.ref_name }}; then + echo "⚠️ Push failed, attempting fetch+rebase and retry..." + git fetch origin + git rebase origin/${{ inputs.branch || github.ref_name }} + if ! git push origin HEAD:${{ inputs.branch || github.ref_name }}; then + echo "❌ Error: Push failed after rebase. Check branch protection rules." + exit 1 + fi + fi + fi + + # Push all tags — tolerate already-existing tags (do not fail the job) + echo "Pushing tags..." + if ! git push origin --tags; then + echo "⚠️ Warning: Some tags may already exist on remote. Continuing." + fi + + echo "✅ Version bumps and tags pushed successfully" + fi + + calculate-artifact-name: + runs-on: ubuntu-latest + outputs: + artifact-name: ${{ steps.calc.outputs.artifact-name }} + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Calculate artifact name + id: calc + uses: ./.github/actions/vscode/calculate-artifact-name + with: + artifact-name: vsix-packages + dry-run: ${{ inputs.dry-run || github.event.inputs.dry-run || 'false' }} + + package: + needs: [bump-versions, calculate-artifact-name, determine-changes] + uses: ./.github/workflows/vscode-package.yml + with: + branch: ${{ inputs.branch || github.ref_name }} + artifact-name: ${{ needs.calculate-artifact-name.outputs.artifact-name }} + dry-run: ${{ inputs.dry-run || github.event.inputs.dry-run || 'false' }} + pre-release: ${{ inputs.pre-release || github.event.inputs.pre-release || 'false' }} + + determine-publish-matrix: + needs: [determine-changes, calculate-artifact-name] + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.matrix.outputs.matrix }} + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '22.x' + + - name: Install dependencies + uses: ./.github/actions/vscode/npm-install-with-retries + + - name: Determine publish matrix + id: matrix + env: + REGISTRIES: ${{ inputs.registries }} + SELECTED_EXTENSIONS: ${{ needs.determine-changes.outputs.selected-extensions }} + IS_NIGHTLY: ${{ inputs.is-nightly }} + EXTENSION_ID: ${{ inputs.extension-id }} + VSIX_GLOB: ${{ inputs.vsix-glob }} + TAG_PREFIX: ${{ inputs.tag-prefix }} + run: | + # Skip marketplace publishing for nightly builds + if [ "$IS_NIGHTLY" = "true" ]; then + echo "Nightly build detected - skipping marketplace publishing" + echo 'matrix=[]' >> $GITHUB_OUTPUT + else + node node_modules/github-workflows/packages/vscode-extension-ci/dist/cli.js ext-publish-matrix + fi + + publish: + needs: + [ + bump-versions, + package, + calculate-artifact-name, + determine-publish-matrix, + ] + runs-on: ubuntu-latest + if: needs.determine-publish-matrix.outputs.matrix != '[]' + strategy: + matrix: + include: ${{ fromJson(needs.determine-publish-matrix.outputs.matrix) }} + steps: + - name: Audit release attempt + shell: bash + run: | + # Create audit log entry for release attempt + AUDIT_LOG="/tmp/release_audit.log" + TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + ACTOR="${{ github.actor }}" + REPO="${{ github.repository }}" + RUN_ID="${{ github.run_id }}" + WORKFLOW="${{ github.workflow }}" + BRANCH="${{ inputs.branch || github.ref_name }}" + + # Log audit information + echo "[$TIMESTAMP] RELEASE_ATTEMPT: actor=$ACTOR, repo=$REPO, run_id=$RUN_ID, workflow=$WORKFLOW, branch=$BRANCH, registry=${{ matrix.registry }}, marketplace=${{ matrix.marketplace }}, dry_run=${{ inputs.dry-run || github.event.inputs.dry-run || 'false' }}" >> "$AUDIT_LOG" + + # Also log to GitHub Actions output for visibility + echo "🔍 AUDIT: Release attempt logged - $TIMESTAMP" + echo " Actor: $ACTOR" + echo " Repository: $REPO" + echo " Run ID: $RUN_ID" + echo " Workflow: $WORKFLOW" + echo " Branch: $BRANCH" + echo " Registry: ${{ matrix.registry }}" + echo " Marketplace: ${{ matrix.marketplace }}" + echo " Dry-run: ${{ inputs.dry-run || github.event.inputs.dry-run || 'false' }}" + + - name: Checkout + uses: actions/checkout@v6 + with: + token: ${{ secrets.IDEE_GH_TOKEN }} + ref: ${{ inputs.branch || github.ref }} + + - name: Download VSIX artifacts + uses: actions/download-artifact@v8 + with: + name: ${{ needs.calculate-artifact-name.outputs.artifact-name }} + path: ./vsix-artifacts + + - name: List downloaded artifacts + run: | + echo "=== DEBUG: Downloaded Artifacts ===" + echo "Artifact name: ${{ needs.calculate-artifact-name.outputs.artifact-name }}" + echo "Download path: ./vsix-artifacts" + echo "" + + if [ -d "./vsix-artifacts" ]; then + echo "Directory exists. Contents:" + ls -la ./vsix-artifacts/ + echo "" + + echo "VSIX files found:" + find ./vsix-artifacts -name "*.vsix" -exec ls -la {} \; + echo "" + + echo "Total VSIX files: $(find ./vsix-artifacts -name "*.vsix" | wc -l)" + else + echo "❌ Directory ./vsix-artifacts does not exist!" + fi + echo "=== END DEBUG ===" + + - name: Find VSIX file for publishing + id: find_vsix + run: | + ARTIFACTS_DIR="./vsix-artifacts" + VSIX_PATTERN="${{ matrix.vsix_pattern }}" + VSIX_FILE=$(find "$ARTIFACTS_DIR" -name "$VSIX_PATTERN" | head -1) + + if [ -z "$VSIX_FILE" ]; then + echo "❌ No VSIX file found matching pattern: $VSIX_PATTERN" + echo "Searching in: $ARTIFACTS_DIR" + echo "Available files:" + find "$ARTIFACTS_DIR" -name "*.vsix" -exec ls -la {} \; + exit 1 + fi + + echo "vsix_file=$VSIX_FILE" >> $GITHUB_OUTPUT + echo "Found VSIX file: $VSIX_FILE" + + - name: Publish to ${{ matrix.marketplace }} + uses: ./.github/actions/vscode/publish-vsix + env: + # Pass tokens as environment variables for better security + VSCE_PERSONAL_ACCESS_TOKEN: ${{ matrix.registry == 'vsce' && secrets.VSCE_PERSONAL_ACCESS_TOKEN || '' }} + OVSX_PAT: ${{ matrix.registry == 'ovsx' && secrets.IDEE_OVSX_PAT || '' }} + with: + vsix-path: ${{ steps.find_vsix.outputs.vsix_file }} + publish-tool: ${{ matrix.registry }} + pre-release: ${{ inputs.pre-release || github.event.inputs.pre-release || 'false' }} + dry-run: ${{ inputs.dry-run || github.event.inputs.dry-run || 'false' }} + + - name: Audit release result + shell: bash + if: inputs.dry-run != 'true' && github.event.inputs.dry-run != 'true' + run: | + # Log the result of the release attempt + AUDIT_LOG="/tmp/release_audit.log" + TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + ACTOR="${{ github.actor }}" + REPO="${{ github.repository }}" + RUN_ID="${{ github.run_id }}" + BRANCH="${{ inputs.branch || github.ref_name }}" + + if [ $? -eq 0 ]; then + echo "[$TIMESTAMP] RELEASE_SUCCESS: actor=$ACTOR, repo=$REPO, run_id=$RUN_ID, branch=$BRANCH, registry=${{ matrix.registry }}, marketplace=${{ matrix.marketplace }}" >> "$AUDIT_LOG" + echo "✅ AUDIT: Release successful - $TIMESTAMP" + else + echo "[$TIMESTAMP] RELEASE_FAILURE: actor=$ACTOR, repo=$REPO, run_id=$RUN_ID, branch=$BRANCH, registry=${{ matrix.registry }}, marketplace=${{ matrix.marketplace }}" >> "$AUDIT_LOG" + echo "❌ AUDIT: Release failed - $TIMESTAMP" + fi + + create-github-releases: + name: Create GitHub Releases + needs: [package, determine-changes, calculate-artifact-name] + runs-on: ubuntu-latest + if: needs.package.result == 'success' && needs.determine-changes.outputs.selected-extensions != '' && github.event_name != 'pull_request' + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ inputs.branch || github.ref }} + token: ${{ secrets.IDEE_GH_TOKEN }} + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '22.x' + + - name: Install dependencies + uses: ./.github/actions/vscode/npm-install-with-retries + + - name: Download VSIX artifacts + uses: actions/download-artifact@v8 + with: + name: ${{ needs.calculate-artifact-name.outputs.artifact-name }} + path: ./vsix-artifacts + + - name: Create GitHub releases + env: + GITHUB_TOKEN: ${{ secrets.IDEE_GH_TOKEN }} + GITHUB_REPOSITORY: ${{ github.repository }} + SELECTED_EXTENSIONS: ${{ needs.determine-changes.outputs.selected-extensions }} + IS_NIGHTLY: ${{ inputs.is-nightly }} + PRE_RELEASE: ${{ inputs.pre-release || 'true' }} + VERSION_BUMP: ${{ needs.determine-changes.outputs.version-bumps }} + DRY_RUN: ${{ inputs.dry-run || github.event.inputs.dry-run || 'false' }} + BRANCH: ${{ inputs.branch || github.ref_name }} + VSIX_ARTIFACTS_PATH: ./vsix-artifacts + EXTENSION_ID: ${{ inputs.extension-id }} + VSIX_GLOB: ${{ inputs.vsix-glob }} + TAG_PREFIX: ${{ inputs.tag-prefix }} + run: | + node node_modules/github-workflows/packages/vscode-extension-ci/dist/cli.js ext-github-releases + + publish-to-cbweb-marketplace: + name: Publish to CBWeb Internal Marketplace + needs: [package, create-github-releases, calculate-artifact-name] + runs-on: ubuntu-latest + continue-on-error: true + if: needs.package.result == 'success' + steps: + - name: Download VSIX artifacts + uses: actions/download-artifact@v8 + with: + name: ${{ needs.calculate-artifact-name.outputs.artifact-name }} + path: ./vsix-artifacts + + - name: Find web-target VSIX for CBWeb + id: find-web-vsix + run: | + VSIX_FILE=$(find ./vsix-artifacts -type f -name "*-web-*.vsix" | head -1) + if [ -z "$VSIX_FILE" ]; then + echo "::error::No web-target VSIX found in artifacts (expected *-web-*.vsix from package workflow)" + exit 1 + fi + + FILE_SIZE=$(stat -c%s "$VSIX_FILE" 2>/dev/null || stat -f%z "$VSIX_FILE" 2>/dev/null || echo "unknown") + echo "Found web VSIX: $VSIX_FILE (${FILE_SIZE} bytes)" + echo "vsix_file=$VSIX_FILE" >> $GITHUB_OUTPUT + + - name: Publish web VSIX to CBWeb internal marketplace + if: inputs.dry-run != 'true' && github.event.inputs.dry-run != 'true' + run: | + echo "Publishing $VSIX_FILE to CBWeb marketplace..." + + HTTP_CODE=$(curl -s -o response.json -w '%{http_code}' \ + --retry 2 --retry-delay 5 \ + -X POST "${MARKETPLACE_URL}/api/internal/publish" \ + -H "Authorization: Bearer ${MARKETPLACE_DEPLOY_TOKEN}" \ + -F "vsix=@${VSIX_FILE}") + + echo "HTTP response code: $HTTP_CODE" + cat response.json + + if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then + echo "Successfully published to CBWeb marketplace" + else + echo "::warning::Failed to publish to CBWeb marketplace (HTTP $HTTP_CODE)" + exit 1 + fi + env: + VSIX_FILE: ${{ steps.find-web-vsix.outputs.vsix_file }} + MARKETPLACE_URL: ${{ vars.MARKETPLACE_URL }} + MARKETPLACE_DEPLOY_TOKEN: ${{ secrets.MARKETPLACE_DEPLOY_TOKEN }} + + - name: Dry-run summary + if: inputs.dry-run == 'true' || github.event.inputs.dry-run == 'true' + run: | + echo "🔄 DRY RUN: Would publish ${{ steps.find-web-vsix.outputs.vsix_file }} to CBWeb marketplace" + + slack-notify: + name: Slack Notification + needs: + [determine-changes, bump-versions, package, publish] + runs-on: ubuntu-latest + if: always() && needs.publish.result == 'success' && (inputs.dry-run != 'true' && github.event.inputs.dry-run != 'true') + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ inputs.branch || github.ref }} + + - name: Get Extension Details + id: extension-details + run: | + # Get selected extensions and their details + SELECTED_EXTENSIONS="${{ needs.determine-changes.outputs.selected-extensions }}" + VERSION_BUMP="${{ needs.determine-changes.outputs.version-bumps }}" + PRE_RELEASE="${{ inputs.pre-release || 'true' }}" + + # Initialize arrays for extension details + EXTENSION_NAMES="" + EXTENSION_VERSIONS="" + EXTENSION_DISPLAY_NAMES="" + + IFS=',' read -ra EXTENSIONS <<< "$SELECTED_EXTENSIONS" + for ext in "${EXTENSIONS[@]}"; do + if [ -n "$ext" ] && [ -f "packages/$ext/package.json" ]; then + # Get package details + PACKAGE_NAME=$(node -p "require('./packages/$ext/package.json').name") + PACKAGE_VERSION=$(node -p "require('./packages/$ext/package.json').version") + DISPLAY_NAME=$(node -p "require('./packages/$ext/package.json').displayName || require('./packages/$ext/package.json').name") + + # Add to arrays + if [ -z "$EXTENSION_NAMES" ]; then + EXTENSION_NAMES="$PACKAGE_NAME" + EXTENSION_VERSIONS="$PACKAGE_VERSION" + EXTENSION_DISPLAY_NAMES="$DISPLAY_NAME" + else + EXTENSION_NAMES="$EXTENSION_NAMES, $PACKAGE_NAME" + EXTENSION_VERSIONS="$EXTENSION_VERSIONS, $PACKAGE_VERSION" + EXTENSION_DISPLAY_NAMES="$EXTENSION_DISPLAY_NAMES, $DISPLAY_NAME" + fi + fi + done + + echo "extension_names=$EXTENSION_NAMES" >> $GITHUB_OUTPUT + echo "extension_versions=$EXTENSION_VERSIONS" >> $GITHUB_OUTPUT + echo "extension_display_names=$EXTENSION_DISPLAY_NAMES" >> $GITHUB_OUTPUT + echo "version_bump=$VERSION_BUMP" >> $GITHUB_OUTPUT + echo "pre_release=$PRE_RELEASE" >> $GITHUB_OUTPUT + + - name: Notify Slack + uses: slackapi/slack-github-action@v3.0.3 + with: + payload: | + { + "text": "🎉 ${{ inputs.slack-name }} Released Successfully!", + "blocks": [ + { + "type": "header", + "text": { + "type": "plain_text", + "text": "🎉 ${{ inputs.slack-name }} Released Successfully!" + } + }, + { + "type": "section", + "fields": [ + { + "type": "mrkdwn", + "text": "*Repository:*\n${{ github.repository }}" + }, + { + "type": "mrkdwn", + "text": "*Branch:*\n${{ inputs.branch || github.ref_name }}" + }, + { + "type": "mrkdwn", + "text": "*Extensions:*\n${{ steps.extension-details.outputs.extension_display_names }}" + }, + { + "type": "mrkdwn", + "text": "*Versions:*\n${{ steps.extension-details.outputs.extension_versions }}" + }, + { + "type": "mrkdwn", + "text": "*Release Type:*\n${{ steps.extension-details.outputs.pre_release == 'true' && 'Pre-release' || 'Stable' }}" + }, + { + "type": "mrkdwn", + "text": "*Version Bump:*\n${{ steps.extension-details.outputs.version_bump }}" + } + ] + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Workflow Run:* <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Details>" + } + } + ] + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.IDEE_MAIN_SLACK_WEBHOOK }} + + slack-notify-failure: + name: Slack Failure Notification + needs: + [determine-changes, bump-versions, package, publish] + runs-on: ubuntu-latest + if: always() && needs.publish.result == 'failure' && (inputs.dry-run != 'true' && github.event.inputs.dry-run != 'true') + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ inputs.branch || github.ref }} + + - name: Get Extension Details + id: extension-details + run: | + # Get selected extensions and their details + SELECTED_EXTENSIONS="${{ needs.determine-changes.outputs.selected-extensions }}" + VERSION_BUMP="${{ needs.determine-changes.outputs.version-bumps }}" + PRE_RELEASE="${{ inputs.pre-release || 'true' }}" + + # Initialize arrays for extension details + EXTENSION_NAMES="" + EXTENSION_VERSIONS="" + EXTENSION_DISPLAY_NAMES="" + + IFS=',' read -ra EXTENSIONS <<< "$SELECTED_EXTENSIONS" + for ext in "${EXTENSIONS[@]}"; do + if [ -n "$ext" ] && [ -f "packages/$ext/package.json" ]; then + # Get package details + PACKAGE_NAME=$(node -p "require('./packages/$ext/package.json').name") + PACKAGE_VERSION=$(node -p "require('./packages/$ext/package.json').version") + DISPLAY_NAME=$(node -p "require('./packages/$ext/package.json').displayName || require('./packages/$ext/package.json').name") + + # Add to arrays + if [ -z "$EXTENSION_NAMES" ]; then + EXTENSION_NAMES="$PACKAGE_NAME" + EXTENSION_VERSIONS="$PACKAGE_VERSION" + EXTENSION_DISPLAY_NAMES="$DISPLAY_NAME" + else + EXTENSION_NAMES="$EXTENSION_NAMES, $PACKAGE_NAME" + EXTENSION_VERSIONS="$EXTENSION_VERSIONS, $PACKAGE_VERSION" + EXTENSION_DISPLAY_NAMES="$EXTENSION_DISPLAY_NAMES, $DISPLAY_NAME" + fi + fi + done + + echo "extension_names=$EXTENSION_NAMES" >> $GITHUB_OUTPUT + echo "extension_versions=$EXTENSION_VERSIONS" >> $GITHUB_OUTPUT + echo "extension_display_names=$EXTENSION_DISPLAY_NAMES" >> $GITHUB_OUTPUT + echo "version_bump=$VERSION_BUMP" >> $GITHUB_OUTPUT + echo "pre_release=$PRE_RELEASE" >> $GITHUB_OUTPUT + + - name: Notify Slack + uses: slackapi/slack-github-action@v3.0.3 + with: + payload: | + { + "text": "❌ VS Code Extension Release Failed!", + "blocks": [ + { + "type": "header", + "text": { + "type": "plain_text", + "text": "❌ VS Code Extension Release Failed!" + } + }, + { + "type": "section", + "fields": [ + { + "type": "mrkdwn", + "text": "*Repository:*\n${{ github.repository }}" + }, + { + "type": "mrkdwn", + "text": "*Branch:*\n${{ inputs.branch || github.ref_name }}" + }, + { + "type": "mrkdwn", + "text": "*Extensions:*\n${{ steps.extension-details.outputs.extension_display_names }}" + }, + { + "type": "mrkdwn", + "text": "*Versions:*\n${{ steps.extension-details.outputs.extension_versions }}" + }, + { + "type": "mrkdwn", + "text": "*Release Type:*\n${{ steps.extension-details.outputs.pre_release == 'true' && 'Pre-release' || 'Stable' }}" + }, + { + "type": "mrkdwn", + "text": "*Version Bump:*\n${{ steps.extension-details.outputs.version_bump }}" + } + ] + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Workflow Run:* <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Details>" + } + }, + { + "type": "context", + "elements": [ + { + "type": "mrkdwn", + "text": "Please check the workflow logs for detailed error information." + } + ] + } + ] + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.IDEE_MAIN_SLACK_WEBHOOK }} diff --git a/.gitignore b/.gitignore index b512c09d..0cec7780 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ -node_modules \ No newline at end of file +node_modules +dist +*.log +.DS_Store +coverage +*.tsbuildinfo \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..3772729d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,4592 @@ +{ + "name": "github-workflows", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "github-workflows", + "version": "1.0.0", + "workspaces": [ + "packages/*" + ], + "devDependencies": {}, + "engines": { + "node": ">=18.0.0", + "npm": ">=9.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.29.7", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz", + "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.29.7", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.29.7", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/reporters/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@jest/reporters/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/reporters/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@jest/reporters/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.1" + } + }, + "node_modules/@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", + "license": "MIT" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@salesforce/vscode-extension-ci": { + "resolved": "packages/vscode-extension-ci", + "link": true + }, + "node_modules/@simple-git/args-pathspec": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@simple-git/args-pathspec/-/args-pathspec-1.0.3.tgz", + "integrity": "sha512-ngJMaHlsWDTfjyq9F3VIQ8b7NXbBLq5j9i5bJ6XLYtD6qlDXT7fdKY2KscWWUF8t18xx052Y/PUO1K1TRc9yKA==", + "license": "MIT" + }, + "node_modules/@simple-git/argv-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@simple-git/argv-parser/-/argv-parser-1.1.1.tgz", + "integrity": "sha512-Q9lBcfQ+VQCpQqGJFHe5yooOS5hGdLFFbJ5R+R5aDsnkPCahtn1hSkMcORX65J2Z5lxSkD0lQorMsncuBQxYUw==", + "license": "MIT", + "dependencies": { + "@simple-git/args-pathspec": "^1.0.3" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.32", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz", + "integrity": "sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001793", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.364", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/electron-to-chromium/-/electron-to-chromium-1.5.364.tgz", + "integrity": "sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/handlebars": { + "version": "4.7.9", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@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" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/jest-config/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-config/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/jest-runtime/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-runtime/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.46", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/node-releases/-/node-releases-2.0.46.tgz", + "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-git": { + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.36.0.tgz", + "integrity": "sha512-cGQjLjK8bxJw4QuYT7gxHw3/IouVESbhahSsHrX97MzCL1gu2u7oy38W6L2ZIGECEfIBG4BabsWDPjBxJENv9Q==", + "license": "MIT", + "dependencies": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "@simple-git/args-pathspec": "^1.0.3", + "@simple-git/argv-parser": "^1.1.0", + "debug": "^4.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/steveukx/git-js?sponsor=1" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-jest": { + "version": "29.4.11", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/ts-jest/-/ts-jest-29.4.11.tgz", + "integrity": "sha512-IrFl7l9AuB/qrNw5quqvAv/hmKMb8dhWOH4jQOGo0Oq8tCeo1O86/iTFG1FaRimgUkF13l4PcepO8ATFT6Ns4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.9", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.8.0", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <7" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "packages/vscode-extension-ci": { + "name": "@salesforce/vscode-extension-ci", + "version": "1.0.0", + "license": "BSD-3-Clause", + "dependencies": { + "chalk": "^5.3.0", + "commander": "^14.0.3", + "glob": "^10.3.10", + "semver": "^7.7.4", + "simple-git": "^3.36.0", + "zod": "^4.4.3" + }, + "bin": { + "vscode-ext-ci": "dist/cli.js" + }, + "devDependencies": { + "@types/jest": "^29.5.5", + "@types/node": "^22.15.1", + "@types/semver": "^7.5.8", + "jest": "^29.7.0", + "ts-jest": "^29.4.1", + "typescript": "^5.9.3" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "packages/vscode-extension-ci/node_modules/chalk": { + "version": "5.6.2", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..eff06950 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "github-workflows", + "version": "1.0.0", + "private": true, + "description": "Shared GitHub Actions workflows and scripts for Salesforce repositories", + "repository": { + "type": "git", + "url": "https://github.com/salesforcecli/github-workflows.git" + }, + "workspaces": [ + "packages/*" + ], + "scripts": { + "build": "npm run build --workspaces --if-present", + "test": "npm run test --workspaces --if-present", + "lint": "npm run lint --workspaces --if-present", + "prepare": "npm run build" + }, + "devDependencies": {}, + "engines": { + "node": ">=18.0.0", + "npm": ">=9.0.0" + } +} diff --git a/packages/vscode-extension-ci/README.md b/packages/vscode-extension-ci/README.md new file mode 100644 index 00000000..6358c8a6 --- /dev/null +++ b/packages/vscode-extension-ci/README.md @@ -0,0 +1,98 @@ +# @salesforce/vscode-extension-ci + +Shared CI/CD infrastructure for Salesforce VS Code extensions. + +## Installation + +```bash +npm install --save-dev @salesforce/vscode-extension-ci +``` + +## CLI Usage + +The package provides a CLI tool for running release automation scripts: + +```bash +npx vscode-ext-ci +``` + +### Available Commands + +**Extension Management:** +- `ext-build-type` - Determine build type (nightly/promotion/regular) +- `ext-change-detector` - Detect changes in extensions and determine version bump +- `ext-nightly-finder` - Find eligible nightly builds for pre-release promotion +- `ext-package-selector` - Discover available VS Code extensions +- `ext-publish-matrix` - Generate publish matrix for marketplace publishing +- `ext-release-plan` - Display extension release plan +- `ext-version-bumper` - Bump versions for selected extensions +- `ext-github-releases` - Create GitHub releases with VSIX artifacts + +**NPM Package Management:** +- `npm-change-detector` - Detect changes in NPM packages +- `npm-package-selector` - Select NPM packages for release +- `npm-package-details` - Extract package details for notifications +- `npm-release-plan` - Generate NPM release plan + +**Utilities:** +- `audit-logger` - Log audit events for compliance + +## Environment Variables + +Configure behavior with environment variables: + +- `PACKAGES_ROOT` - Root directory for packages (default: `packages`) +- `TAG_PREFIX` - Git tag prefix (default: `marketplace`) +- `AUDIT_LOG_DIR` - Audit log directory (default: `.github/audit-logs`) + +## Programmatic API + +```typescript +import { + detectExtensionChanges, + bumpVersions, + createGitHubReleases, + determinePublishMatrix +} from '@salesforce/vscode-extension-ci'; + +// Detect changes +const changes = await detectExtensionChanges(buildContext, commitSha, extensions); + +// Bump versions +bumpVersions({ + versionBump: 'auto', + selectedExtensions: 'my-extension', + preRelease: 'true', + isNightly: 'true' +}); +``` + +## Features + +### Smart Version Bumping + +Uses conventional commits and even/odd minor versioning: + +- **Even minor** (0.2.x, 0.4.x) → Stable releases +- **Odd minor** (0.3.x, 0.5.x) → Pre-releases +- `fix:` commits → patch bump +- `feat:` commits → minor bump +- `feat!:` or `BREAKING CHANGE:` → major bump + +### Change Detection + +Analyzes git history and conventional commits to determine: +- Which extensions have changes +- What type of version bump is needed +- Whether to create a release + +### GitHub Releases + +Automatically creates GitHub releases with: +- VSIX artifacts attached +- Release notes from commits +- Proper tagging (pre-release vs stable) + +## License + +BSD-3-Clause diff --git a/packages/vscode-extension-ci/jest.config.cjs b/packages/vscode-extension-ci/jest.config.cjs new file mode 100644 index 00000000..a3b0408d --- /dev/null +++ b/packages/vscode-extension-ci/jest.config.cjs @@ -0,0 +1,24 @@ +/** @type {import('jest').Config} */ +module.exports = { + testEnvironment: 'node', + testMatch: ['**/__tests__/**/*.test.ts', '**/?(*.)+(test).ts'], + preset: 'ts-jest/presets/default-esm', + extensionsToTreatAsEsm: ['.ts'], + moduleNameMapper: { + '^(\\.{1,2}/.*)\\.js$': '$1', + }, + transform: { + '^.+\\.tsx?$': [ + 'ts-jest', + { + useESM: true, + tsconfig: { + module: 'esnext', + target: 'es2022', + moduleResolution: 'bundler', + }, + }, + ], + }, + verbose: true, +}; diff --git a/packages/vscode-extension-ci/package.json b/packages/vscode-extension-ci/package.json new file mode 100644 index 00000000..c6ae9b2d --- /dev/null +++ b/packages/vscode-extension-ci/package.json @@ -0,0 +1,62 @@ +{ + "name": "@salesforce/vscode-extension-ci", + "version": "1.0.0", + "description": "Shared CI/CD infrastructure for Salesforce VS Code extensions", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "bin": { + "vscode-ext-ci": "./dist/cli.js" + }, + "scripts": { + "build": "tsc", + "clean": "rm -rf dist", + "test": "NODE_OPTIONS=--experimental-vm-modules jest", + "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch", + "lint": "eslint src --ext .ts", + "prepare": "npm run build", + "prepublishOnly": "npm run build" + }, + "files": [ + "dist", + "README.md", + "LICENSE" + ], + "keywords": [ + "vscode", + "extension", + "ci", + "cd", + "release", + "automation", + "salesforce" + ], + "repository": { + "type": "git", + "url": "https://github.com/salesforcecli/github-workflows.git", + "directory": "packages/vscode-extension-ci" + }, + "license": "BSD-3-Clause", + "dependencies": { + "commander": "^14.0.3", + "chalk": "^5.3.0", + "zod": "^4.4.3", + "semver": "^7.7.4", + "simple-git": "^3.36.0", + "glob": "^10.3.10" + }, + "devDependencies": { + "@types/jest": "^29.5.5", + "@types/node": "^22.15.1", + "@types/semver": "^7.5.8", + "jest": "^29.7.0", + "ts-jest": "^29.4.1", + "typescript": "^5.9.3" + }, + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=22.0.0" + } +} diff --git a/packages/vscode-extension-ci/src/cli.ts b/packages/vscode-extension-ci/src/cli.ts new file mode 100644 index 00000000..98cb636b --- /dev/null +++ b/packages/vscode-extension-ci/src/cli.ts @@ -0,0 +1,301 @@ +#!/usr/bin/env node + +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE file in the + * repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { Command } from 'commander'; +import { determineBuildType, setBuildTypeOutputs } from './extension/ext-build-type.js'; +import { + findNightlyCandidate, + setNightlyFinderOutputs, +} from './extension/ext-nightly-finder.js'; +import { + detectExtensionChanges, + setChangeDetectionOutputs, +} from './extension/ext-change-detector.js'; +import { + getAvailableExtensions, + setExtensionDiscoveryOutputs, +} from './extension/ext-package-selector.js'; + +import { + detectNpmChanges, + setNpmChangeDetectionOutputs, +} from './npm/npm-change-detector.js'; +import { npmPackageSelectorMain } from './npm/npm-package-selector.js'; + +import { + extractPackageDetails, + setPackageDetailsOutputs, +} from './npm/npm-package-details.js'; +import { generateReleasePlan, displayReleasePlan } from './npm/npm-release-plan.js'; +import { displayExtensionReleasePlan } from './extension/ext-release-plan.js'; +import { bumpVersions } from './extension/ext-version-bumper.js'; +import { determinePublishMatrix } from './extension/ext-publish-matrix.js'; +import { createGitHubReleases } from './extension/ext-github-releases.js'; +import { logAuditEvent } from './core/audit-logger.js'; + +import { log, setOutput } from './core/utils.js'; + +const program = new Command(); + +program + .name('release-scripts') + .description('Release automation scripts for VS Code extensions') + .version('1.0.0'); + +program + .command('ext-build-type') + .description('Determine build type (nightly/promotion/regular)') + .action(async () => { + try { + const buildContext = determineBuildType(); + setBuildTypeOutputs(buildContext); + } catch (error) { + log.error(`Failed to determine build type: ${error}`); + process.exit(1); + } + }); + +program + .command('ext-nightly-finder') + .description('Find eligible nightly builds for pre-release promotion') + .action(async () => { + try { + const candidate = await findNightlyCandidate(); + setNightlyFinderOutputs(candidate); + } catch (error) { + log.error(`Failed to find nightly candidate: ${error}`); + process.exit(1); + } + }); + +program + .command('ext-change-detector') + .description('Detect changes in extensions') + .action(async () => { + try { + // Parse build context from environment variables + const isNightly = process.env.IS_NIGHTLY === 'true'; + const versionBump = (process.env.VERSION_BUMP as any) || 'auto'; + const preRelease = process.env.PRE_RELEASE === 'true'; + const isPromotion = process.env.IS_PROMOTION === 'true'; + const promotionCommitSha = process.env.PROMOTION_COMMIT_SHA; + const userSelectedExtensions = process.env.SELECTED_EXTENSIONS; + + const buildContext = { + isNightly, + versionBump, + preRelease, + isPromotion, + promotionCommitSha, + }; + + const result = await detectExtensionChanges( + buildContext, + promotionCommitSha, + userSelectedExtensions, + ); + setChangeDetectionOutputs(result); + } catch (error) { + log.error(`Failed to determine changes: ${error}`); + process.exit(1); + } + }); + +program + .command('npm-change-detector') + .description('Detect changes in NPM packages') + .action(async () => { + try { + const baseBranch = process.env.INPUT_BASE_BRANCH || 'main'; + const result = await detectNpmChanges(baseBranch); + setNpmChangeDetectionOutputs(result); + } catch (error) { + log.error(`Failed to detect NPM changes: ${error}`); + process.exit(1); + } + }); + +program + .command('npm-package-selector') + .description( + 'Discover available NPM packages or select packages based on user input', + ) + .action(async () => { + try { + await npmPackageSelectorMain(); + } catch (error) { + log.error(`Failed to handle NPM packages: ${error}`); + process.exit(1); + } + }); + +program + .command('ext-package-selector') + .description('Discover available VS Code extensions') + .action(async () => { + try { + const extensions = getAvailableExtensions(); + setExtensionDiscoveryOutputs(extensions); + } catch (error) { + log.error(`Failed to discover extensions: ${error}`); + process.exit(1); + } + }); + +program + .command('npm-package-details') + .description('Extract NPM package details for notifications') + .action(async () => { + try { + const selectedPackagesJson = process.env.SELECTED_PACKAGES || '[]'; + const versionBump = process.env.VERSION_BUMP || 'patch'; + + const details = extractPackageDetails( + selectedPackagesJson, + versionBump as any, + ); + setPackageDetailsOutputs(details); + } catch (error) { + log.error(`Failed to extract package details: ${error}`); + process.exit(1); + } + }); + +program + .command('npm-release-plan') + .description('Generate NPM release plan') + .action(async () => { + try { + const packageName = process.env.MATRIX_PACKAGE; + const versionBump = process.env.VERSION_BUMP || 'patch'; + const dryRun = process.env.DRY_RUN === 'true'; + + if (!packageName) { + log.error('MATRIX_PACKAGE environment variable is required'); + process.exit(1); + } + + const plan = generateReleasePlan(packageName, versionBump as any, dryRun); + if (plan) { + displayReleasePlan(plan); + } else { + log.error('Failed to generate release plan'); + process.exit(1); + } + } catch (error) { + log.error(`Failed to generate release plan: ${error}`); + process.exit(1); + } + }); + +program + .command('ext-release-plan') + .description('Display extension release plan for dry runs') + .action(async () => { + try { + const options = { + branch: process.env.BRANCH || 'main', + buildType: process.env.BUILD_TYPE || 'workflow_dispatch', + isNightly: process.env.IS_NIGHTLY || 'false', + versionBump: process.env.VERSION_BUMP || 'auto', + registries: process.env.REGISTRIES || 'all', + preRelease: process.env.PRE_RELEASE || 'false', + selectedExtensions: process.env.SELECTED_EXTENSIONS || '', + }; + displayExtensionReleasePlan(options); + } catch (error) { + log.error(`Failed to display release plan: ${error}`); + process.exit(1); + } + }); + +program + .command('audit-logger') + .description('Log audit events for release operations') + .action(async () => { + try { + logAuditEvent({ + action: process.env.ACTION || '', + actor: process.env.ACTOR || '', + repository: process.env.REPOSITORY || '', + branch: process.env.BRANCH || '', + workflow: process.env.WORKFLOW || '', + runId: process.env.RUN_ID || '', + details: process.env.DETAILS || '{}', + logFile: process.env.LOG_FILE, + }); + } catch (error) { + log.error(`Failed to log audit event: ${error}`); + process.exit(1); + } + }); + +program + .command('ext-github-releases') + .description('Create GitHub releases for extensions') + .action(async () => { + try { + createGitHubReleases({ + dryRun: process.env.DRY_RUN === 'true', + preRelease: process.env.PRE_RELEASE || 'false', + versionBump: process.env.VERSION_BUMP || 'auto', + selectedExtensions: process.env.SELECTED_EXTENSIONS || '', + isNightly: process.env.IS_NIGHTLY || 'false', + vsixArtifactsPath: + process.env.VSIX_ARTIFACTS_PATH || './vsix-artifacts', + }); + } catch (error) { + log.error(`Failed to create GitHub releases: ${error}`); + process.exit(1); + } + }); + +program + .command('ext-publish-matrix') + .description('Determine publish matrix for extensions') + .action(async () => { + try { + const options = { + registries: process.env.REGISTRIES || 'all', + selectedExtensions: process.env.SELECTED_EXTENSIONS || '', + }; + const matrix = determinePublishMatrix(options); + // Output in GitHub Actions format + setOutput('matrix', JSON.stringify(matrix)); + } catch (error) { + log.error(`Failed to determine publish matrix: ${error}`); + process.exit(1); + } + }); + +program + .command('ext-version-bumper') + .description('Bump versions for selected extensions') + .action(async () => { + try { + bumpVersions({ + selectedExtensions: process.env.SELECTED_EXTENSIONS || '', + preRelease: process.env.PRE_RELEASE || 'false', + isNightly: process.env.IS_NIGHTLY || 'false', + extensionId: process.env.EXTENSION_ID, + newMajor: process.env.NEW_MAJOR, + }); + } catch (error) { + log.error(`Failed to bump versions: ${error}`); + process.exit(1); + } + }); + +// Show help if no command provided +if (process.argv.length === 2) { + program.help(); +} + +program.parse(); diff --git a/packages/vscode-extension-ci/src/core/audit-logger.ts b/packages/vscode-extension-ci/src/core/audit-logger.ts new file mode 100644 index 00000000..02e1c3fd --- /dev/null +++ b/packages/vscode-extension-ci/src/core/audit-logger.ts @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the + * repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { appendFileSync, existsSync, mkdirSync } from 'fs'; +import { join } from 'path'; + +interface AuditLogEntry { + timestamp: string; + action: string; + actor: string; + repository: string; + branch: string; + workflow: string; + runId: string; + details: Record; +} + +interface AuditLoggerOptions { + action: string; + actor: string; + repository: string; + branch: string; + workflow: string; + runId: string; + details: string; + logFile?: string; +} + +function getAuditLogPath(): string { + const auditLogDir = process.env.AUDIT_LOG_DIR || '.github/audit-logs'; + const logDir = join(process.cwd(), auditLogDir); + const logFile = join(logDir, 'release-audit.log'); + + // Ensure log directory exists + if (!existsSync(logDir)) { + // Create directory if it doesn't exist + mkdirSync(logDir, { recursive: true }); + } + + return logFile; +} + +function formatAuditEntry(entry: AuditLogEntry): string { + const timestamp = new Date().toISOString(); + const details = JSON.stringify(entry.details, null, 2); + + // eslint-disable-next-line max-len + const header = `[${timestamp}] ${entry.action} | Actor: ${entry.actor} | Repo: ${entry.repository} | Branch: ${entry.branch} | Workflow: ${entry.workflow} | Run: ${entry.runId}`; + const separator = '-'.repeat(80); + + return `${header}\nDetails: ${details}\n${separator}\n`; +} + +function logAuditEvent(options: AuditLoggerOptions): void { + const { + action, + actor, + repository, + branch, + workflow, + runId, + details, + logFile, + } = options; + + try { + // Parse details JSON + const parsedDetails = JSON.parse(details); + + const entry: AuditLogEntry = { + timestamp: new Date().toISOString(), + action, + actor, + repository, + branch, + workflow, + runId, + details: parsedDetails, + }; + + const auditLogPath = logFile || getAuditLogPath(); + const logEntry = formatAuditEntry(entry); + + // Append to audit log + appendFileSync(auditLogPath, logEntry, 'utf-8'); + + console.log(`✅ Audit log entry written to: ${auditLogPath}`); + console.log(`Action: ${action}`); + console.log(`Actor: ${actor}`); + console.log(`Repository: ${repository}`); + console.log(`Branch: ${branch}`); + console.log(`Workflow: ${workflow}`); + console.log(`Run ID: ${runId}`); + console.log('Details:', JSON.stringify(parsedDetails, null, 2)); + } catch (error) { + console.error('Failed to write audit log entry:', error); + throw error; + } +} + +// Export for use in other modules +export { logAuditEvent }; diff --git a/packages/vscode-extension-ci/src/core/types.ts b/packages/vscode-extension-ci/src/core/types.ts new file mode 100644 index 00000000..03cd4d6e --- /dev/null +++ b/packages/vscode-extension-ci/src/core/types.ts @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the + * repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +export interface BuildContext { + isNightly: boolean; + versionBump: VersionBumpType; + preRelease: boolean; + isPromotion: boolean; + promotionCommitSha?: string; +} + +export type VersionBumpType = 'patch' | 'minor' | 'major' | 'auto'; + +export interface ExtensionInfo { + name: string; + path: string; + currentVersion: string; + publisher?: string; + displayName?: string; +} + +export interface ChangeDetectionResult { + selectedExtensions: string[]; + versionBumps: VersionBumpType; + promotionCommitSha?: string; +} + +export interface PromotionCandidate { + tag: string; + commitSha: string; + commitDate: number; + version: string; +} + +export interface VersionBumpResult { + packageName: string; + oldVersion: string; + newVersion: string; + bumpType: VersionBumpType; + strategy: 'nightly' | 'promotion' | 'regular'; +} + +export interface ReleasePlan { + extensions: ExtensionReleasePlan[]; + buildType: BuildContext; + dryRun: boolean; +} + +export interface ExtensionReleasePlan { + name: string; + currentVersion: string; + newVersion: string; + publisher: string; + displayName: string; + bumpType: VersionBumpType; + strategy: 'nightly' | 'promotion' | 'regular'; + registries: string[]; +} + +export interface GitTag { + name: string; + commitSha: string; + commitDate: number; + isStable: boolean; + isNightly: boolean; + version?: string; +} + +export interface Environment { + githubEventName: string; + githubRef: string; + githubRefName: string; + githubActor: string; + githubRepository: string; + githubRunId: string; + githubWorkflow: string; + inputs: { + branch?: string; + extensions?: string; + registries?: string; + dryRun?: string; + preRelease?: string; + versionBump?: string; + }; +} + +/** + * Type representing a semantic version string (major.minor.patch) + */ +export type SemanticVersion = `${number}.${number}.${number}`; + +/** + * Type representing a tag with its extracted version + */ +export type TagWithVersion = { tag: string; version: SemanticVersion | null }; diff --git a/packages/vscode-extension-ci/src/core/utils.ts b/packages/vscode-extension-ci/src/core/utils.ts new file mode 100644 index 00000000..7eaa940d --- /dev/null +++ b/packages/vscode-extension-ci/src/core/utils.ts @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the + * repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { readFileSync, existsSync, appendFileSync } from 'fs'; +import { join } from 'path'; +import { z } from 'zod'; +import chalk from 'chalk'; +import { execSync } from 'child_process'; +import type { SemanticVersion } from './types.js'; +import semver from 'semver'; + +/** + * Parse version string into components + */ +export function parseVersion(version: string): { + major: number; + minor: number; + patch: number; +} { + const parts = version.split('.').map(Number); + if (parts.length !== 3 || parts.some(isNaN)) { + throw new Error(`Invalid version format: ${version}`); + } + return { major: parts[0], minor: parts[1], patch: parts[2] }; +} + +/** + * Format version components back to string + */ +export function formatVersion( + major: number, + minor: number, + patch: number, +): string { + return `${major}.${minor}.${patch}`; +} + +/** + * Check if a version has an even minor (stable) or odd minor (pre-release) + */ +export function isStableVersion(version: string): boolean { + const { minor } = parseVersion(version); + return minor % 2 === 0; +} + +/** + * Check if a version has an odd minor (pre-release) + */ +export function isPreReleaseVersion(version: string): boolean { + return !isStableVersion(version); +} + +/** + * Read and parse package.json + */ +export function readPackageJson(packagePath: string): any { + const packageJsonPath = join(packagePath, 'package.json'); + if (!existsSync(packageJsonPath)) { + throw new Error(`package.json not found at: ${packageJsonPath}`); + } + + const content = readFileSync(packageJsonPath, 'utf-8'); + return JSON.parse(content); +} + +/** + * Get extension information from package.json + */ +export function getExtensionInfo(packagePath: string): { + name: string; + version: string; + publisher?: string; + displayName?: string; +} { + const pkg = readPackageJson(packagePath); + return { + name: pkg.name, + version: pkg.version, + publisher: pkg.publisher, + displayName: pkg.displayName || pkg.name, + }; +} + +/** + * Parse GitHub environment variables + */ +export function parseEnvironment(): { + githubEventName: string; + githubRef: string; + githubRefName: string; + githubActor: string; + githubRepository: string; + githubRunId: string; + githubWorkflow: string; + inputs: Record; +} { + return { + githubEventName: process.env.GITHUB_EVENT_NAME || '', + githubRef: process.env.GITHUB_REF || '', + githubRefName: process.env.GITHUB_REF_NAME || '', + githubActor: process.env.GITHUB_ACTOR || '', + githubRepository: process.env.GITHUB_REPOSITORY || '', + githubRunId: process.env.GITHUB_RUN_ID || '', + githubWorkflow: process.env.GITHUB_WORKFLOW || '', + inputs: { + branch: process.env.INPUT_BRANCH, + extensions: process.env.INPUT_EXTENSIONS, + registries: process.env.INPUT_REGISTRIES, + dryRun: process.env.INPUT_DRY_RUN, + preRelease: process.env.INPUT_PRE_RELEASE, + versionBump: process.env.INPUT_VERSION_BUMP, + }, + }; +} + +/** + * Set GitHub Actions output using environment files (GITHUB_OUTPUT) + */ +export function setOutput(name: string, value: string): void { + const githubOutput = process.env['GITHUB_OUTPUT']; + if (githubOutput) { + appendFileSync(githubOutput, `${name}=${value}\n`); + } else { + // Fallback for local development outside GitHub Actions + console.log(`[output] ${name}=${value}`); + } +} + +/** + * Type guard to check if a string is a valid semantic version + */ +export function isSemanticVersion(version: string): version is SemanticVersion { + return semver.valid(version) !== null; +} + +/** + * Parse semantic version string into components + */ +export function parseSemver(version: SemanticVersion): { + major: number; + minor: number; + patch: number; +} { + const parsed = semver.parse(version); + if (!parsed) { + throw new Error(`Invalid semantic version: ${version}`); + } + return { + major: parsed.major, + minor: parsed.minor, + patch: parsed.patch, + }; +} + +/** + * Compare two semantic versions + * Returns: -1 if a < b, 0 if a === b, 1 if a > b + */ +export function compareSemver(a: SemanticVersion, b: SemanticVersion): number { + return semver.compare(a, b); +} + +/** + * Extract semantic version from tag using regex pattern + */ +export function extractVersionFromTag(tag: string): SemanticVersion | null { + // Match semantic version pattern: v followed by major.minor.patch + const versionMatch = tag.match(/v(\d+\.\d+\.\d+)/); + if (!versionMatch) return null; + + const version = versionMatch[1]; + return isSemanticVersion(version) ? version : null; +} + +/** + * Log with color coding + */ +export const log = { + info: (message: string) => console.log(chalk.blue(`ℹ️ ${message}`)), + success: (message: string) => console.log(chalk.green(`✅ ${message}`)), + warning: (message: string) => console.log(chalk.yellow(`⚠️ ${message}`)), + error: (message: string) => console.log(chalk.red(`❌ ${message}`)), + debug: (message: string) => console.log(chalk.gray(`🔍 ${message}`)), +}; + +/** + * Validate string is not empty + */ +export const nonEmptyString = z.string().min(1); + +/** + * Validate boolean string + */ +export const booleanString = z + .enum(['true', 'false']) + .transform((val) => val === 'true'); + +/** + * Validate version bump type + */ +export const versionBumpType = z.enum(['patch', 'minor', 'major', 'auto']); diff --git a/packages/vscode-extension-ci/src/extension/__tests__/ext-github-releases.test.ts b/packages/vscode-extension-ci/src/extension/__tests__/ext-github-releases.test.ts new file mode 100644 index 00000000..c8391175 --- /dev/null +++ b/packages/vscode-extension-ci/src/extension/__tests__/ext-github-releases.test.ts @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the + * repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { jest } from '@jest/globals'; + +// Mock fs and child_process and glob before importing the module under test. +jest.unstable_mockModule('fs', () => ({ + readFileSync: jest.fn(), + writeFileSync: jest.fn(), + unlinkSync: jest.fn(), +})); +jest.unstable_mockModule('child_process', () => ({ + execFileSync: jest.fn(), +})); +jest.unstable_mockModule('glob', () => ({ + glob: { sync: jest.fn() }, +})); + +const fs = await import('fs'); +const glob = await import('glob'); +const { createGitHubReleases } = await import('../ext-github-releases.js'); + +const mockedReadFileSync = fs.readFileSync as jest.MockedFunction< + typeof fs.readFileSync +>; +const mockedGlobSync = glob.glob.sync as jest.MockedFunction< + typeof glob.glob.sync +>; + +const originalEnv = process.env; +let consoleWarnSpy: jest.SpiedFunction; +let consoleLogSpy: jest.SpiedFunction; + +beforeEach(() => { + process.env = { ...originalEnv }; + delete process.env.VSIX_GLOB; + consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); +}); + +afterEach(() => { + jest.restoreAllMocks(); + jest.clearAllMocks(); +}); + +afterAll(() => { + process.env = originalEnv; +}); + +describe('createGitHubReleases VSIX_GLOB handling', () => { + it('uses VSIX_GLOB to resolve the VSIX glob pattern via glob.sync', () => { + process.env.VSIX_GLOB = 'salesforcedx-vscode-*.vsix'; + mockedReadFileSync.mockReturnValue( + JSON.stringify({ name: 'salesforcedx-vscode', version: '65.9.0' }), + ); + mockedGlobSync.mockReturnValue(['/tmp/vsix-artifacts/salesforcedx-vscode-65.9.0.vsix']); + + createGitHubReleases({ + dryRun: true, + preRelease: 'true', + versionBump: 'minor', + selectedExtensions: 'salesforcedx-vscode', + isNightly: 'false', + vsixArtifactsPath: './vsix-artifacts', + }); + + // glob.sync should have been invoked with paths that contain the env-supplied glob + const seenPatterns = mockedGlobSync.mock.calls.map((call) => call[0] as string); + expect(seenPatterns.some((p) => p.includes('salesforcedx-vscode-*.vsix'))).toBe(true); + }); + + it('throws when VSIX_GLOB is not set', () => { + delete process.env.VSIX_GLOB; + mockedReadFileSync.mockReturnValue( + JSON.stringify({ name: 'salesforcedx-vscode', version: '65.9.0' }), + ); + + expect(() => + createGitHubReleases({ + dryRun: true, + preRelease: 'true', + versionBump: 'minor', + selectedExtensions: 'salesforcedx-vscode', + isNightly: 'false', + vsixArtifactsPath: './vsix-artifacts', + }), + ).toThrow(/VSIX_GLOB env var is required/); + }); + + it('skips an extension whose package.json cannot be read', () => { + process.env.VSIX_GLOB = 'salesforcedx-vscode-*.vsix'; + mockedReadFileSync.mockImplementation(() => { + throw new Error('ENOENT'); + }); + + createGitHubReleases({ + dryRun: true, + preRelease: 'true', + versionBump: 'minor', + selectedExtensions: 'no-such-package', + isNightly: 'false', + vsixArtifactsPath: './vsix-artifacts', + }); + + expect(consoleWarnSpy).toHaveBeenCalledWith( + expect.stringContaining('Could not read package.json'), + expect.any(Error), + ); + }); + + it('warns when VSIX_GLOB is set but no files match', () => { + process.env.VSIX_GLOB = 'no-match-*.vsix'; + mockedReadFileSync.mockReturnValue( + JSON.stringify({ name: 'salesforcedx-vscode', version: '65.9.0' }), + ); + mockedGlobSync.mockReturnValue([]); + + createGitHubReleases({ + dryRun: true, + preRelease: 'true', + versionBump: 'minor', + selectedExtensions: 'salesforcedx-vscode', + isNightly: 'false', + vsixArtifactsPath: './vsix-artifacts', + }); + + expect(consoleWarnSpy).toHaveBeenCalledWith( + expect.stringContaining('No VSIX files found for salesforcedx-vscode'), + ); + // dry-run summary should still be emitted + expect(consoleLogSpy).toHaveBeenCalledWith( + expect.stringContaining('DRY RUN: GitHub release simulation completed'), + ); + }); + + it('uses per-extension pattern when VSIX_GLOB is a JSON map', () => { + process.env.VSIX_GLOB = JSON.stringify({ + core: 'salesforcedx-vscode-core-*.vsix', + apex: 'salesforcedx-vscode-apex-*.vsix', + }); + mockedReadFileSync.mockReturnValue( + JSON.stringify({ name: 'core', version: '1.0.0' }), + ); + mockedGlobSync.mockReturnValue(['/tmp/vsix-artifacts/core/salesforcedx-vscode-core-1.0.0.vsix']); + + createGitHubReleases({ + dryRun: true, + preRelease: 'true', + versionBump: 'minor', + selectedExtensions: 'core', + isNightly: 'false', + vsixArtifactsPath: './vsix-artifacts', + }); + + const seenPatterns = mockedGlobSync.mock.calls.map((call) => call[0] as string); + expect(seenPatterns.some((p) => p.includes('salesforcedx-vscode-core-*.vsix'))).toBe(true); + expect(seenPatterns.some((p) => p.includes('salesforcedx-vscode-apex-*.vsix'))).toBe(false); + }); + + it('throws when VSIX_GLOB JSON map has no entry for the extension', () => { + process.env.VSIX_GLOB = JSON.stringify({ + core: 'salesforcedx-vscode-core-*.vsix', + }); + mockedReadFileSync.mockReturnValue( + JSON.stringify({ name: 'apex', version: '1.0.0' }), + ); + + expect(() => + createGitHubReleases({ + dryRun: true, + preRelease: 'true', + versionBump: 'minor', + selectedExtensions: 'apex', + isNightly: 'false', + vsixArtifactsPath: './vsix-artifacts', + }), + ).toThrow(/VSIX_GLOB map has no entry for extension 'apex'/); + }); + + it('throws when VSIX_GLOB looks like JSON but fails to parse', () => { + process.env.VSIX_GLOB = '{not valid json'; + mockedReadFileSync.mockReturnValue( + JSON.stringify({ name: 'core', version: '1.0.0' }), + ); + + expect(() => + createGitHubReleases({ + dryRun: true, + preRelease: 'true', + versionBump: 'minor', + selectedExtensions: 'core', + isNightly: 'false', + vsixArtifactsPath: './vsix-artifacts', + }), + ).toThrow(/VSIX_GLOB looks like JSON but failed to parse/); + }); +}); diff --git a/packages/vscode-extension-ci/src/extension/__tests__/ext-nightly-finder.test.ts b/packages/vscode-extension-ci/src/extension/__tests__/ext-nightly-finder.test.ts new file mode 100644 index 00000000..1098a3c2 --- /dev/null +++ b/packages/vscode-extension-ci/src/extension/__tests__/ext-nightly-finder.test.ts @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the + * repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { jest } from '@jest/globals'; + +interface MockTagMeta { + name: string; + hash: string; + isoDate: string; +} + +const tagsState: { tags: MockTagMeta[] } = { tags: [] }; + +const mockGitInstance = { + tags: jest.fn(async () => ({ + all: tagsState.tags.map((t) => t.name), + latest: tagsState.tags[0]?.name, + })), + log: jest.fn(async (opts: { from: string; to: string }) => { + const found = tagsState.tags.find((t) => t.name === opts.from); + if (!found) { + return { latest: undefined }; + } + return { + latest: { hash: found.hash, date: found.isoDate }, + }; + }), +}; + +jest.unstable_mockModule('simple-git', () => ({ + default: jest.fn(() => mockGitInstance), +})); + +const { findNightlyCandidate } = await import('../ext-nightly-finder.js'); + +const originalEnv = process.env; + +function setTags(tags: MockTagMeta[]): void { + tagsState.tags = tags; +} + +beforeEach(() => { + process.env = { ...originalEnv }; + delete process.env.EXTENSION_ID; + delete process.env.TAG_PREFIX; + delete process.env.MIN_TAG_AGE_DAYS; + setTags([]); + jest.spyOn(console, 'log').mockImplementation(() => {}); + jest.spyOn(console, 'warn').mockImplementation(() => {}); +}); + +afterEach(() => { + jest.restoreAllMocks(); + jest.clearAllMocks(); +}); + +afterAll(() => { + process.env = originalEnv; +}); + +describe('findNightlyCandidate EXTENSION_ID handling', () => { + it('throws when EXTENSION_ID is missing', async () => { + delete process.env.EXTENSION_ID; + await expect(findNightlyCandidate()).rejects.toThrow( + /EXTENSION_ID env var is required/, + ); + }); + + it('returns a candidate using the configured EXTENSION_ID for tracking-tag exclusion', async () => { + process.env.EXTENSION_ID = 'salesforce.salesforcedx-vscode'; + process.env.MIN_TAG_AGE_DAYS = '7'; + + // Date 30 days ago — passes age filter + const oldIso = new Date(Date.now() - 30 * 86400 * 1000).toISOString(); + setTags([ + { name: 'v66.7.0-nightly.20260101', hash: 'abc123', isoDate: oldIso }, + ]); + + const result = await findNightlyCandidate(); + expect(result).not.toBeNull(); + expect(result?.tag).toBe('v66.7.0-nightly.20260101'); + expect(result?.commitSha).toBe('abc123'); + }); + + it('skips a nightly when a tracking tag for the configured EXTENSION_ID already exists', async () => { + process.env.EXTENSION_ID = 'salesforce.salesforcedx-vscode'; + process.env.MIN_TAG_AGE_DAYS = '7'; + + const oldIso = new Date(Date.now() - 30 * 86400 * 1000).toISOString(); + setTags([ + { name: 'v66.7.0-nightly.20260101', hash: 'abc123', isoDate: oldIso }, + { + name: 'marketplace-prerelease-salesforce.salesforcedx-vscode-v66.7.0', + hash: 'def456', + isoDate: oldIso, + }, + ]); + + const result = await findNightlyCandidate(); + expect(result).toBeNull(); + }); + + it('does not skip a nightly when only a different-extension tracking tag exists', async () => { + process.env.EXTENSION_ID = 'salesforce.salesforcedx-vscode'; + process.env.MIN_TAG_AGE_DAYS = '7'; + + const oldIso = new Date(Date.now() - 30 * 86400 * 1000).toISOString(); + setTags([ + { name: 'v66.7.0-nightly.20260101', hash: 'abc123', isoDate: oldIso }, + { + name: 'marketplace-prerelease-some.other-extension-v66.7.0', + hash: 'def456', + isoDate: oldIso, + }, + ]); + + const result = await findNightlyCandidate(); + expect(result?.tag).toBe('v66.7.0-nightly.20260101'); + }); + + it('honors a custom TAG_PREFIX in the tracking-tag check', async () => { + process.env.EXTENSION_ID = 'salesforce.salesforcedx-vscode'; + process.env.TAG_PREFIX = 'mp'; + process.env.MIN_TAG_AGE_DAYS = '7'; + + const oldIso = new Date(Date.now() - 30 * 86400 * 1000).toISOString(); + setTags([ + { name: 'v66.7.0-nightly.20260101', hash: 'abc123', isoDate: oldIso }, + { + name: 'mp-prerelease-salesforce.salesforcedx-vscode-v66.7.0', + hash: 'def456', + isoDate: oldIso, + }, + ]); + + const result = await findNightlyCandidate(); + expect(result).toBeNull(); + }); +}); diff --git a/packages/vscode-extension-ci/src/extension/__tests__/ext-publish-matrix.test.ts b/packages/vscode-extension-ci/src/extension/__tests__/ext-publish-matrix.test.ts new file mode 100644 index 00000000..0a8f9e62 --- /dev/null +++ b/packages/vscode-extension-ci/src/extension/__tests__/ext-publish-matrix.test.ts @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the + * repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { jest } from '@jest/globals'; + +// Mock fs to avoid scanning real packages dir during tests. +jest.unstable_mockModule('fs', () => ({ + readdirSync: jest.fn(() => []), + existsSync: jest.fn(() => false), + readFileSync: jest.fn(), + appendFileSync: jest.fn(), + writeFileSync: jest.fn(), + unlinkSync: jest.fn(), +})); + +const { determinePublishMatrix } = await import('../ext-publish-matrix.js'); + +const originalEnv = process.env; + +beforeEach(() => { + process.env = { ...originalEnv }; + delete process.env.VSIX_GLOB; + jest.spyOn(console, 'log').mockImplementation(() => {}); + jest.spyOn(console, 'warn').mockImplementation(() => {}); +}); + +afterEach(() => { + jest.restoreAllMocks(); + jest.clearAllMocks(); +}); + +afterAll(() => { + process.env = originalEnv; +}); + +describe('determinePublishMatrix VSIX_GLOB handling', () => { + it('uses VSIX_GLOB env var as the vsix_pattern for each entry', () => { + process.env.VSIX_GLOB = 'salesforcedx-vscode-*.vsix'; + const matrix = determinePublishMatrix({ + registries: 'all', + selectedExtensions: 'salesforcedx-vscode', + }); + expect(matrix).toHaveLength(2); + expect(matrix.every((e) => e.vsix_pattern === 'salesforcedx-vscode-*.vsix')).toBe( + true, + ); + }); + + it('throws when VSIX_GLOB is missing and a real selection is supplied', () => { + delete process.env.VSIX_GLOB; + expect(() => + determinePublishMatrix({ + registries: 'vsce', + selectedExtensions: 'salesforcedx-vscode', + }), + ).toThrow(/VSIX_GLOB env var is required/); + }); + + it('returns empty matrix without consulting VSIX_GLOB when selection is empty', () => { + delete process.env.VSIX_GLOB; + expect( + determinePublishMatrix({ + registries: 'all', + selectedExtensions: '', + }), + ).toEqual([]); + }); + + it('returns empty matrix without consulting VSIX_GLOB when selection is "none"', () => { + delete process.env.VSIX_GLOB; + expect( + determinePublishMatrix({ + registries: 'all', + selectedExtensions: 'none', + }), + ).toEqual([]); + }); + + it('uses per-extension glob when VSIX_GLOB is a JSON map', () => { + process.env.VSIX_GLOB = JSON.stringify({ + core: 'salesforcedx-vscode-core-*.vsix', + apex: 'salesforcedx-vscode-apex-*.vsix', + }); + const matrix = determinePublishMatrix({ + registries: 'vsce', + selectedExtensions: 'core,apex', + }); + expect(matrix).toHaveLength(2); + expect(matrix[0].vsix_pattern).toBe('salesforcedx-vscode-core-*.vsix'); + expect(matrix[1].vsix_pattern).toBe('salesforcedx-vscode-apex-*.vsix'); + }); + + it('throws with helpful message when VSIX_GLOB JSON map is missing entry for extension', () => { + process.env.VSIX_GLOB = JSON.stringify({ + core: 'salesforcedx-vscode-core-*.vsix', + }); + expect(() => + determinePublishMatrix({ + registries: 'vsce', + selectedExtensions: 'apex', + }), + ).toThrow(/VSIX_GLOB map has no entry for extension 'apex'.*Available keys: core/); + }); + + it('throws parse error when VSIX_GLOB starts with { but is not valid JSON', () => { + process.env.VSIX_GLOB = '{not valid json'; + expect(() => + determinePublishMatrix({ + registries: 'vsce', + selectedExtensions: 'core', + }), + ).toThrow(/VSIX_GLOB looks like JSON but failed to parse/); + }); +}); diff --git a/packages/vscode-extension-ci/src/extension/__tests__/ext-version-bumper.test.ts b/packages/vscode-extension-ci/src/extension/__tests__/ext-version-bumper.test.ts new file mode 100644 index 00000000..93cba0ab --- /dev/null +++ b/packages/vscode-extension-ci/src/extension/__tests__/ext-version-bumper.test.ts @@ -0,0 +1,508 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the + * repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { jest } from '@jest/globals'; + +// Mock child_process and fs BEFORE importing the module under test. +jest.unstable_mockModule('child_process', () => ({ + execFileSync: jest.fn(), +})); +jest.unstable_mockModule('fs', () => ({ + readFileSync: jest.fn(), + appendFileSync: jest.fn(), +})); + +const { execFileSync } = await import('child_process'); +const { + buildNextVersion, + isCI, + errorAndExit, + validateNewMajor, + parseSemver, + getLatestPreReleaseVersionFromMarketplace, +} = await import('../ext-version-bumper.js'); + +const mockedExecFileSync = execFileSync as jest.MockedFunction< + typeof execFileSync +>; + +const originalEnv = process.env; +let consoleSpy: jest.SpiedFunction; +let consoleWarnSpy: jest.SpiedFunction; +let exitSpy: jest.SpiedFunction; + +beforeEach(() => { + process.env = { ...originalEnv }; + delete process.env.CI; + delete process.env.NEW_MAJOR; + delete process.env.FORCE_NEW_MAJOR; + consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); + consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + exitSpy = jest + .spyOn(process, 'exit') + .mockImplementation((() => {}) as never); +}); + +afterEach(() => { + consoleSpy.mockRestore(); + consoleWarnSpy.mockRestore(); + exitSpy.mockRestore(); + jest.clearAllMocks(); +}); + +afterAll(() => { + process.env = originalEnv; +}); + +// --------------------------------------------------------------------------- +// isCI +// --------------------------------------------------------------------------- +describe('isCI', () => { + it('returns true when CI env var is "true"', () => { + process.env.CI = 'true'; + expect(isCI()).toBe(true); + }); + + it('returns false when CI env var is "false"', () => { + process.env.CI = 'false'; + expect(isCI()).toBe(false); + }); + + it('returns false when CI env var is undefined', () => { + delete process.env.CI; + expect(isCI()).toBe(false); + }); + + it('returns false when CI env var is empty string', () => { + process.env.CI = ''; + expect(isCI()).toBe(false); + }); + + it('returns false when CI is "TRUE" (case sensitive)', () => { + process.env.CI = 'TRUE'; + expect(isCI()).toBe(false); + }); +}); + +// --------------------------------------------------------------------------- +// errorAndExit +// --------------------------------------------------------------------------- +describe('errorAndExit', () => { + describe('in CI environment', () => { + beforeEach(() => { + process.env.CI = 'true'; + }); + + it('logs message with GitHub Actions error prefix', () => { + errorAndExit('Something went wrong'); + expect(consoleSpy).toHaveBeenCalledWith('::error::Something went wrong'); + }); + + it('exits with code 1', () => { + errorAndExit('Something went wrong'); + expect(exitSpy).toHaveBeenCalledWith(1); + }); + }); + + describe('in local environment', () => { + beforeEach(() => { + delete process.env.CI; + }); + + it('logs message with colored error prefix', () => { + errorAndExit('Something went wrong'); + expect(consoleSpy).toHaveBeenCalledWith( + '\x1b[31m[Error]\x1b[0m Something went wrong', + ); + }); + + it('exits with code 0 (to prevent terminal from closing)', () => { + errorAndExit('Something went wrong'); + expect(exitSpy).toHaveBeenCalledWith(0); + }); + }); +}); + +// --------------------------------------------------------------------------- +// validateNewMajor +// --------------------------------------------------------------------------- +describe('validateNewMajor', () => { + it('returns undefined when NEW_MAJOR is not set', () => { + delete process.env.NEW_MAJOR; + expect(validateNewMajor()).toBeUndefined(); + }); + + it('returns undefined when NEW_MAJOR is empty string', () => { + process.env.NEW_MAJOR = ''; + expect(validateNewMajor()).toBeUndefined(); + }); + + it('returns parsed integer when NEW_MAJOR is a valid whole number', () => { + process.env.NEW_MAJOR = '66'; + expect(validateNewMajor()).toBe(66); + }); + + it('returns parsed integer for single digit', () => { + process.env.NEW_MAJOR = '5'; + expect(validateNewMajor()).toBe(5); + }); + + it('accepts an explicit raw value (overrides env)', () => { + process.env.NEW_MAJOR = '66'; + expect(validateNewMajor('70')).toBe(70); + }); + + it('returns undefined when explicit raw value is empty', () => { + process.env.NEW_MAJOR = '66'; + expect(validateNewMajor('')).toBeUndefined(); + }); + + it('calls errorAndExit when NEW_MAJOR contains a decimal point', () => { + process.env.NEW_MAJOR = '66.0'; + validateNewMajor(); + expect(exitSpy).toHaveBeenCalled(); + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining('Invalid NEW_MAJOR value (66.0)'), + ); + }); + + it('calls errorAndExit when NEW_MAJOR is not a number', () => { + process.env.NEW_MAJOR = 'abc'; + validateNewMajor(); + expect(exitSpy).toHaveBeenCalled(); + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining('Invalid NEW_MAJOR value (abc)'), + ); + }); + + it('calls errorAndExit when NEW_MAJOR is a semver string', () => { + process.env.NEW_MAJOR = '66.1.0'; + validateNewMajor(); + expect(exitSpy).toHaveBeenCalled(); + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining('Invalid NEW_MAJOR value (66.1.0)'), + ); + }); +}); + +// --------------------------------------------------------------------------- +// parseSemver +// --------------------------------------------------------------------------- +describe('parseSemver', () => { + describe('valid versions', () => { + it('parses standard semver', () => { + expect(parseSemver('65.8.0')).toEqual({ + semver: '65.8.0', + major: 65, + minor: 8, + patch: 0, + }); + }); + + it('parses single digit versions', () => { + expect(parseSemver('1.2.3')).toEqual({ + semver: '1.2.3', + major: 1, + minor: 2, + patch: 3, + }); + }); + + it('parses large version numbers', () => { + expect(parseSemver('100.200.300')).toEqual({ + semver: '100.200.300', + major: 100, + minor: 200, + patch: 300, + }); + }); + + it('parses version with zeros', () => { + expect(parseSemver('0.0.0')).toEqual({ + semver: '0.0.0', + major: 0, + minor: 0, + patch: 0, + }); + }); + }); + + describe('invalid versions', () => { + it('calls errorAndExit for prerelease versions', () => { + parseSemver('1.2.3-beta.0'); + expect(exitSpy).toHaveBeenCalled(); + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining('Prerelease versions'), + ); + }); + + it('calls errorAndExit for prerelease with simple tag', () => { + parseSemver('1.2.3-alpha'); + expect(exitSpy).toHaveBeenCalled(); + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining('Prerelease versions'), + ); + }); + + it('calls errorAndExit for missing patch version', () => { + parseSemver('1.2'); + expect(exitSpy).toHaveBeenCalled(); + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining('Invalid version format: 1.2'), + ); + }); + + it('calls errorAndExit for missing minor and patch', () => { + parseSemver('1'); + expect(exitSpy).toHaveBeenCalled(); + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining('Invalid version format: 1'), + ); + }); + + it('calls errorAndExit for non-numeric version', () => { + parseSemver('a.b.c'); + expect(exitSpy).toHaveBeenCalled(); + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining('Invalid version format: a.b.c'), + ); + }); + + it('calls errorAndExit for empty string', () => { + parseSemver(''); + expect(exitSpy).toHaveBeenCalled(); + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining('Invalid version format:'), + ); + }); + + it('calls errorAndExit for version without dots', () => { + parseSemver('123'); + expect(exitSpy).toHaveBeenCalled(); + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining('Invalid version format: 123'), + ); + }); + }); +}); + +// --------------------------------------------------------------------------- +// getLatestPreReleaseVersionFromMarketplace +// --------------------------------------------------------------------------- +describe('getLatestPreReleaseVersionFromMarketplace', () => { + const createMockResponse = (versions: any[]) => + JSON.stringify({ versions }); + + const createVersion = (version: string, isPreRelease = false) => ({ + version, + properties: isPreRelease + ? [{ key: 'Microsoft.VisualStudio.Code.PreRelease', value: 'true' }] + : [], + }); + + it('returns the latest pre-release version', () => { + const mockResponse = createMockResponse([ + createVersion('65.9.0', true), + createVersion('65.8.0', true), + createVersion('65.7.0', false), + ]); + mockedExecFileSync.mockReturnValue(Buffer.from(mockResponse)); + + const result = getLatestPreReleaseVersionFromMarketplace( + 'salesforce.salesforcedx-vscode', + ); + + expect(result).toBe('65.9.0'); + expect(mockedExecFileSync).toHaveBeenCalledWith('npx', [ + '@vscode/vsce', + 'show', + 'salesforce.salesforcedx-vscode', + '--json', + ]); + }); + + it('skips non-pre-release versions to find the latest pre-release', () => { + const mockResponse = createMockResponse([ + createVersion('65.9.0', false), + createVersion('65.8.0', false), + createVersion('65.7.0', true), + ]); + mockedExecFileSync.mockReturnValue(Buffer.from(mockResponse)); + + const result = getLatestPreReleaseVersionFromMarketplace( + 'salesforce.salesforcedx-vscode', + ); + + expect(result).toBe('65.7.0'); + }); + + it('throws when vsce returns "undefined" (extension not found)', () => { + mockedExecFileSync.mockReturnValue(Buffer.from('undefined')); + + expect(() => + getLatestPreReleaseVersionFromMarketplace('some.extension'), + ).toThrow(/No version info found/); + }); + + it('returns null when no pre-release versions exist (bootstrap case)', () => { + const mockResponse = createMockResponse([ + createVersion('65.9.0', false), + createVersion('65.8.0', false), + ]); + mockedExecFileSync.mockReturnValue(Buffer.from(mockResponse)); + + const result = getLatestPreReleaseVersionFromMarketplace( + 'salesforce.salesforcedx-vscode', + ); + + expect(result).toBeNull(); + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining('Treating as bootstrap'), + ); + }); + + it('handles versions with no properties array', () => { + const mockResponse = JSON.stringify({ + versions: [ + { version: '65.9.0' }, + createVersion('65.8.0', true), + ], + }); + mockedExecFileSync.mockReturnValue(Buffer.from(mockResponse)); + + const result = getLatestPreReleaseVersionFromMarketplace( + 'salesforce.salesforcedx-vscode', + ); + + expect(result).toBe('65.8.0'); + }); + + it('throws when execFileSync itself fails (network/rate-limit)', () => { + mockedExecFileSync.mockImplementation(() => { + throw new Error('ENETUNREACH'); + }); + + expect(() => + getLatestPreReleaseVersionFromMarketplace('some.extension'), + ).toThrow(/Failed to query marketplace/); + }); +}); + +// --------------------------------------------------------------------------- +// buildNextVersion +// --------------------------------------------------------------------------- +describe('buildNextVersion', () => { + describe('without newMajor', () => { + it('bumps MINOR when main and marketplace versions match', () => { + const main = parseSemver('65.8.0'); + const marketplace = parseSemver('65.8.0'); + expect(buildNextVersion(main, marketplace, undefined)).toBe('65.9.0'); + }); + + it('bumps PATCH when main minor is greater than marketplace', () => { + const main = parseSemver('65.9.0'); + const marketplace = parseSemver('65.8.0'); + expect(buildNextVersion(main, marketplace, undefined)).toBe('65.9.1'); + }); + + it('bumps PATCH when main major is already ahead', () => { + const main = parseSemver('66.0.0'); + const marketplace = parseSemver('65.8.0'); + expect(buildNextVersion(main, marketplace, undefined)).toBe('66.0.1'); + }); + + it('bumps PATCH correctly when patch is already > 0', () => { + const main = parseSemver('65.9.5'); + const marketplace = parseSemver('65.8.0'); + expect(buildNextVersion(main, marketplace, undefined)).toBe('65.9.6'); + }); + + it('bumps MINOR from main when marketplace is null (bootstrap)', () => { + const main = parseSemver('65.8.0'); + expect(buildNextVersion(main, null, undefined)).toBe('65.9.0'); + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining('Bootstrap: no marketplace prerelease yet'), + ); + }); + + it('throws with diagnostic when main is behind marketplace (fall-through)', () => { + const main = parseSemver('65.7.0'); + const marketplace = parseSemver('65.8.0'); + expect(() => buildNextVersion(main, marketplace, undefined)).toThrow( + /main \(65\.7\.0\) is behind marketplace prerelease \(65\.8\.0\)/, + ); + }); + + it('throws with diagnostic when main is behind marketplace on major', () => { + const main = parseSemver('64.5.0'); + const marketplace = parseSemver('65.8.0'); + expect(() => buildNextVersion(main, marketplace, undefined)).toThrow( + /main \(64\.5\.0\) is behind marketplace prerelease \(65\.8\.0\)/, + ); + }); + }); + + describe('with newMajor', () => { + beforeEach(() => { + delete process.env.FORCE_NEW_MAJOR; + }); + + it('returns new major version when validation passes', () => { + const main = parseSemver('65.8.0'); + const marketplace = parseSemver('65.8.0'); + expect(buildNextVersion(main, marketplace, 66)).toBe('66.0.0'); + }); + + it('works when marketplace is null (bootstrap + newMajor)', () => { + const main = parseSemver('65.8.0'); + expect(buildNextVersion(main, null, 66)).toBe('66.0.0'); + }); + + it('calls errorAndExit when main and marketplace majors do not match', () => { + const main = parseSemver('66.0.0'); + const marketplace = parseSemver('65.8.0'); + buildNextVersion(main, marketplace, 67); + expect(exitSpy).toHaveBeenCalled(); + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining('major versions'), + ); + }); + + it('calls errorAndExit when newMajor is not exactly 1 greater than main', () => { + const main = parseSemver('65.8.0'); + const marketplace = parseSemver('65.8.0'); + buildNextVersion(main, marketplace, 68); + expect(exitSpy).toHaveBeenCalled(); + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining('not exactly 1 greater'), + ); + }); + + describe('with FORCE_NEW_MAJOR', () => { + beforeEach(() => { + process.env.FORCE_NEW_MAJOR = 'true'; + }); + + it('bypasses validation checks', () => { + const main = parseSemver('65.8.0'); + const marketplace = parseSemver('64.0.0'); + expect(buildNextVersion(main, marketplace, 99)).toBe('99.0.0'); + expect(exitSpy).not.toHaveBeenCalled(); + }); + + it('logs warning about bypassing checks', () => { + const main = parseSemver('65.8.0'); + const marketplace = parseSemver('65.8.0'); + buildNextVersion(main, marketplace, 66); + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining('FORCE_NEW_MAJOR'), + ); + }); + }); + }); +}); diff --git a/packages/vscode-extension-ci/src/extension/ext-build-type.ts b/packages/vscode-extension-ci/src/extension/ext-build-type.ts new file mode 100644 index 00000000..2d2f9edd --- /dev/null +++ b/packages/vscode-extension-ci/src/extension/ext-build-type.ts @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the + * repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { BuildContext, VersionBumpType } from '../core/types.js'; +import { setOutput, log, booleanString, versionBumpType } from '../core/utils.js'; + +/** + * Determine the build context based on GitHub event and inputs + */ +export function determineBuildType(): BuildContext { + log.info('Determining build type...'); + log.debug(`GitHub event: ${process.env.GITHUB_EVENT_NAME}`); + log.debug(`Pre-release input: ${process.env.INPUT_PRE_RELEASE}`); + log.debug(`Version bump input: ${process.env.INPUT_VERSION_BUMP}`); + + // Check if this is a scheduled nightly build + const isNightly = process.env.GITHUB_EVENT_NAME === 'schedule'; + + // Determine version bump type + let versionBump: VersionBumpType = 'auto'; + if (isNightly) { + versionBump = 'patch'; + } else { + const inputBump = process.env.INPUT_VERSION_BUMP || 'auto'; + try { + versionBump = versionBumpType.parse(inputBump); + } catch { + log.warning( + `Invalid version bump type: ${inputBump}, defaulting to 'auto'`, + ); + versionBump = 'auto'; + } + } + + // Determine pre-release status + let preRelease = false; + if (isNightly) { + preRelease = true; + } else { + const inputPreRelease = process.env.INPUT_PRE_RELEASE || 'false'; + try { + preRelease = booleanString.parse(inputPreRelease); + } catch { + log.warning( + `Invalid pre-release value: ${inputPreRelease}, defaulting to false`, + ); + preRelease = false; + } + } + + // Determine if this is a promotion (stable release) + const isPromotion = !preRelease && !isNightly; + + const buildContext: BuildContext = { + isNightly, + versionBump, + preRelease, + isPromotion, + }; + + log.info('Build type determined:'); + log.info(` Is nightly: ${isNightly}`); + log.info(` Version bump: ${versionBump}`); + log.info(` Pre-release: ${preRelease}`); + log.info(` Is promotion: ${isPromotion}`); + + return buildContext; +} + +/** + * Set GitHub Actions outputs for build type + */ +export function setBuildTypeOutputs(buildContext: BuildContext): void { + setOutput('is-nightly', buildContext.isNightly.toString()); + setOutput('version-bump', buildContext.versionBump); + setOutput('pre-release', buildContext.preRelease.toString()); + setOutput('is-promotion', buildContext.isPromotion.toString()); + + log.success('Build type outputs set'); +} + +/** + * Main function for CLI usage + */ +export async function main(): Promise { + try { + const buildContext = determineBuildType(); + setBuildTypeOutputs(buildContext); + } catch (error) { + log.error(`Failed to determine build type: ${error}`); + process.exit(1); + } +} + +// Run if called directly +if (import.meta.url === `file://${process.argv[1]}`) { + main(); +} diff --git a/packages/vscode-extension-ci/src/extension/ext-change-detector.ts b/packages/vscode-extension-ci/src/extension/ext-change-detector.ts new file mode 100644 index 00000000..b61ffc18 --- /dev/null +++ b/packages/vscode-extension-ci/src/extension/ext-change-detector.ts @@ -0,0 +1,426 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the + * repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { simpleGit } from 'simple-git'; +import { readdirSync, existsSync } from 'fs'; +import { join } from 'path'; +import { + BuildContext, + ChangeDetectionResult, + ExtensionInfo, + TagWithVersion, + SemanticVersion, +} from '../core/types.js'; +import { + log, + setOutput, + getExtensionInfo, + compareSemver, + extractVersionFromTag, +} from '../core/utils.js'; + +/** + * Get all available VS Code extensions (packages with publisher field) + */ +function getAvailableExtensions(): ExtensionInfo[] { + const extensions: ExtensionInfo[] = []; + const packagesRoot = process.env.PACKAGES_ROOT || 'packages'; + const packagesDir = join(process.cwd(), packagesRoot); + + if (!existsSync(packagesDir)) { + log.warning('packages directory not found'); + return extensions; + } + + const packageDirs = readdirSync(packagesDir, { withFileTypes: true }) + .filter((dirent) => dirent.isDirectory()) + .map((dirent) => dirent.name); + + for (const packageName of packageDirs) { + const packagePath = join(packagesDir, packageName); + const packageJsonPath = join(packagePath, 'package.json'); + + if (existsSync(packageJsonPath)) { + try { + const info = getExtensionInfo(packagePath); + + // Only include packages that have a publisher (VS Code extensions) + if (info.publisher) { + extensions.push({ + name: packageName, + path: packagePath, + currentVersion: info.version, + publisher: info.publisher, + displayName: info.displayName, + }); + log.debug( + `Found VS Code extension: ${packageName} (publisher: ${info.publisher})`, + ); + } else { + log.debug(`Skipping NPM package: ${packageName} (no publisher)`); + } + } catch (error) { + log.warning(`Failed to read package.json for ${packageName}: ${error}`); + } + } + } + + return extensions; +} + +/** + * Check if extension has changes since last release + */ +async function hasExtensionChanges( + git: any, + extensionPath: string, + lastTag: string | null, +): Promise { + if (!lastTag) { + // No previous tag, check if extension has any files + const files = readdirSync(extensionPath, { recursive: true }); + return files.length > 0; + } + + try { + // Check for changes since the last release tag + const diff = await git.diff([lastTag, 'HEAD', '--', extensionPath]); + return diff.trim().length > 0; + } catch (error) { + log.warning(`Failed to check changes for ${extensionPath}: ${error}`); + return false; + } +} + +/** + * Find the last release tag for a specific extension + */ +async function findLastReleaseTagForExtension( + git: any, + extensionName: string, +): Promise { + try { + const tags = await git.tags(); + const allTags: TagWithVersion[] = tags.all + .filter((tag: string) => tag.startsWith(`${extensionName}-v`)) + .map((tag: string) => { + const version = extractVersionFromTag(tag); + return { tag, version }; + }); + + const extensionTags = allTags + .filter((item): item is { tag: string; version: SemanticVersion } => item.version !== null) // Filter out tags we couldn't parse + .sort((a, b) => + // Use proper semver comparison (descending order - newest first) + compareSemver(b.version, a.version), + ); + + return extensionTags.length > 0 ? extensionTags[0].tag : null; + } catch (error) { + log.warning(`Failed to get tags for ${extensionName}: ${error}`); + return null; + } +} + +/** + * Parse user-selected extensions from environment variable + */ +function parseUserSelectedExtensions( + selectedExtensionsInput?: string, +): string[] { + if (!selectedExtensionsInput || selectedExtensionsInput.trim() === '') { + log.info('No user selection provided - will use all available extensions'); + return []; + } + + const selected = selectedExtensionsInput + .split(',') + .map((s) => s.trim()) + .filter(Boolean); + log.info(`User selected extensions: ${selected.join(', ')}`); + return selected; +} + +/** + * Intersect user selection with detected changes + */ +function intersectExtensions( + userSelected: string[], + changedExtensions: string[], + availableExtensions: ExtensionInfo[], + buildContext: BuildContext, +): string[] { + const availableNames = availableExtensions.map((e) => e.name); + + // If no user selection, use all changed extensions + if (userSelected.length === 0) { + log.info('No user selection - using all detected changes'); + return changedExtensions; + } + + // Handle special values + const normalizedSelection = userSelected.map((s) => s.toLowerCase()); + + if (normalizedSelection.includes('none')) { + log.info('User selected "none" - returning empty selection'); + return []; + } + + if (normalizedSelection.includes('all')) { + log.info('User selected "all" - using all available extensions'); + return availableNames; + } + + if (normalizedSelection.includes('changed')) { + log.info('User selected "changed" - using all detected changes'); + return changedExtensions; + } + + // Validate user selection against available extensions + const validUserSelected = userSelected.filter((ext) => { + if (!availableNames.includes(ext)) { + log.warning( + `User selected extension '${ext}' is not available - skipping`, + ); + return false; + } + return true; + }); + + if (validUserSelected.length === 0) { + log.warning('No valid extensions in user selection'); + return []; + } + + // For nightly builds and promotions, use user selection if provided + if (buildContext.isNightly || buildContext.isPromotion) { + const buildType = buildContext.isNightly ? 'Nightly' : 'Promotion'; + log.info( + `${buildType} build - using user selection: ${validUserSelected.join(', ')}`, + ); + return validUserSelected; + } + + // For regular builds, intersect user selection with detected changes + const intersection = validUserSelected.filter((ext) => + changedExtensions.includes(ext), + ); + + log.info(`User selection: ${validUserSelected.join(', ')}`); + log.info(`Detected changes: ${changedExtensions.join(', ')}`); + log.info(`Intersection: ${intersection.join(', ')}`); + + return intersection; +} + +/** + * Determine the highest required version bump from conventional commits since a tag. + * Returns 'major', 'minor', or 'patch'. + */ +async function detectBumpTypeFromCommits( + git: any, + extensionPath: string, + lastTag: string | null, +): Promise<'major' | 'minor' | 'patch'> { + try { + const range = lastTag ? `${lastTag}..HEAD` : 'HEAD'; + const log_ = await git.log({ + from: lastTag || undefined, + to: 'HEAD', + '--': null, + _: [extensionPath], + }); + const messages: string[] = log_.all.map((c: any) => c.message as string); + + let bump: 'major' | 'minor' | 'patch' = 'patch'; + for (const msg of messages) { + const firstLine = msg.split('\n')[0]; + const body = msg; + if ( + /BREAKING CHANGE/i.test(body) || + /^[a-z]+(\([^)]*\))?!:/i.test(firstLine) + ) { + return 'major'; + } + if (/^feat(\([^)]*\))?:/i.test(firstLine)) { + bump = 'minor'; + } + } + log.debug(`Detected bump type from commits (${range}): ${bump}`); + return bump; + } catch (error) { + log.warning(`Failed to analyze commits for bump type: ${error}`); + return 'patch'; + } +} + +/** + * Detect changes in extensions + */ +export async function detectExtensionChanges( + buildContext: BuildContext, + promotionCommitSha?: string, + userSelectedExtensions?: string, +): Promise { + log.info('Detecting changes in extensions...'); + log.debug(`Build context: ${JSON.stringify(buildContext)}`); + log.debug(`Promotion commit SHA: ${promotionCommitSha || 'none'}`); + log.debug(`User selected extensions: ${userSelectedExtensions || 'none'}`); + + const git = simpleGit(); + const extensions = getAvailableExtensions(); + const changedExtensions: string[] = []; + let versionBumps = buildContext.versionBump; + + log.info( + `Found ${extensions.length} extensions: ${extensions.map((e) => e.name).join(', ')}`, + ); + + // Parse user selection + const userSelected = parseUserSelectedExtensions(userSelectedExtensions); + + // For promotions, always include all extensions + if (buildContext.isPromotion) { + log.info('Promotion detected - including all extensions'); + changedExtensions.push(...extensions.map((e) => e.name)); + } + // For nightly and regular builds, check for changes since last release + else { + const buildType = buildContext.isNightly ? 'Nightly' : 'Regular'; + log.info(`${buildType} build - checking for changes...`); + + for (const extension of extensions) { + log.debug(`Checking extension: ${extension.name}`); + + // Find the last release tag for this specific extension + const lastTag = await findLastReleaseTagForExtension(git, extension.name); + + if (lastTag) { + log.info( + `Comparing ${extension.name} against last release tag: ${lastTag}`, + ); + } else { + log.info( + `No previous release tag found for ${extension.name} - treating as first release`, + ); + } + + const hasChanges = await hasExtensionChanges( + git, + extension.path, + lastTag, + ); + + if (hasChanges) { + log.info( + `Found changes in ${extension.name} - including in ${buildType.toLowerCase()} release`, + ); + changedExtensions.push(extension.name); + + // For nightly builds with auto bump type, analyze conventional commits + // to determine the appropriate bump level + if ( + buildContext.isNightly && + (buildContext.versionBump === 'auto' || + buildContext.versionBump === 'patch') + ) { + const detectedBump = await detectBumpTypeFromCommits( + git, + extension.path, + lastTag, + ); + if ( + detectedBump === 'major' || + (detectedBump === 'minor' && versionBumps !== 'major') + ) { + log.info( + `Upgrading bump type for ${extension.name}: ${versionBumps} → ${detectedBump} (conventional commits)`, + ); + versionBumps = detectedBump; + } + } + } else { + log.info( + `No changes found in ${extension.name} - skipping ${buildType.toLowerCase()} release`, + ); + } + } + } + + // Intersect user selection with detected changes + const finalSelectedExtensions = intersectExtensions( + userSelected, + changedExtensions, + extensions, + buildContext, + ); + + log.info(`Final selected extensions: ${finalSelectedExtensions.join(', ')}`); + log.info(`Version bump type: ${versionBumps}`); + + return { + selectedExtensions: finalSelectedExtensions, + versionBumps, + promotionCommitSha, + }; +} + +/** + * Set GitHub Actions outputs for change detection + */ +export function setChangeDetectionOutputs(result: ChangeDetectionResult): void { + setOutput('selected-extensions', result.selectedExtensions.join(',')); + setOutput('version-bumps', result.versionBumps); + if (result.promotionCommitSha) { + setOutput('promotion-commit-sha', result.promotionCommitSha); + } + + log.success('Change detection outputs set'); +} + +/** + * Main function for CLI usage + */ +export async function main(): Promise { + try { + // For CLI usage, we need to parse the build context from environment + // This would typically come from the previous job's outputs + const isNightly = process.env.IS_NIGHTLY === 'true'; + const versionBump = (process.env.VERSION_BUMP as any) || 'auto'; + const preRelease = process.env.PRE_RELEASE === 'true'; + const isPromotion = process.env.IS_PROMOTION === 'true'; + const promotionCommitSha = process.env.PROMOTION_COMMIT_SHA; + const userSelectedExtensions = process.env.SELECTED_EXTENSIONS; + log.info( + `Raw SELECTED_EXTENSIONS env var: "${process.env.SELECTED_EXTENSIONS}"`, + ); + + const buildContext: BuildContext = { + isNightly, + versionBump, + preRelease, + isPromotion, + promotionCommitSha, + }; + + const result = await detectExtensionChanges( + buildContext, + promotionCommitSha, + userSelectedExtensions, + ); + setChangeDetectionOutputs(result); + } catch (error) { + log.error(`Failed to determine changes: ${error}`); + process.exit(1); + } +} + +// Run if called directly +if (import.meta.url === `file://${process.argv[1]}`) { + main(); +} diff --git a/packages/vscode-extension-ci/src/extension/ext-github-releases.ts b/packages/vscode-extension-ci/src/extension/ext-github-releases.ts new file mode 100644 index 00000000..17d0fc93 --- /dev/null +++ b/packages/vscode-extension-ci/src/extension/ext-github-releases.ts @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the + * repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { execFileSync } from 'child_process'; +import { readFileSync, writeFileSync, unlinkSync } from 'fs'; +import { join } from 'path'; +import { glob } from 'glob'; + +interface PackageJson { + name: string; + version: string; + publisher?: string; + displayName?: string; +} + +interface GitHubReleaseOptions { + dryRun: boolean; + preRelease: string; + versionBump: string; + selectedExtensions: string; + isNightly: string; + vsixArtifactsPath: string; +} + +function getPackageDetails(extensionPath: string): PackageJson | null { + try { + const packageJsonPath = join( + process.cwd(), + 'packages', + extensionPath, + 'package.json', + ); + const content = readFileSync(packageJsonPath, 'utf-8'); + return JSON.parse(content); + } catch (error) { + console.warn( + `Warning: Could not read package.json for ${extensionPath}:`, + error, + ); + return null; + } +} + +function resolveVsixGlob(extension: string): string { + const vsixGlob = process.env.VSIX_GLOB; + if (!vsixGlob) { + throw new Error('VSIX_GLOB env var is required'); + } + if (vsixGlob.trim().startsWith('{')) { + let map: Record; + try { + map = JSON.parse(vsixGlob); + } catch (err) { + throw new Error( + `VSIX_GLOB looks like JSON but failed to parse: ${(err as Error).message}`, + ); + } + const pattern = map[extension]; + if (!pattern) { + throw new Error( + `VSIX_GLOB map has no entry for extension '${extension}'.`, + ); + } + return pattern; + } + return vsixGlob; +} + +function findVsixFiles(extension: string, artifactsPath: string): string[] { + const vsixPattern = resolveVsixGlob(extension); + + // Artifacts are organized in subdirectories: vsix-artifacts/extension-name/file.vsix + // Try both the subdirectory and root level + const patterns = [ + join(artifactsPath, extension, vsixPattern), // Subdirectory structure + join(artifactsPath, '**', vsixPattern), // Recursive search as fallback + join(artifactsPath, vsixPattern), // Root level as fallback + ]; + + const foundFiles: string[] = []; + for (const pattern of patterns) { + const files = glob.sync(pattern); + if (files.length > 0) { + foundFiles.push(...files); + break; // Found files, no need to check other patterns + } + } + + return foundFiles; +} + +function generateReleaseNotes( + extension: string, + currentVersion: string, + isNightly: string, + preRelease: string, +): string { + let releaseNotes = `## ${extension} v${currentVersion}\n\n`; + releaseNotes += '### Changes\n\n'; + + try { + // Find the last release tag for this extension + // Replaces shell pipeline `git tag --sort=-version:refname | grep "^v" | head -1` + // with execFileSync + JS-side filtering (no shell expansion). + const allTags = execFileSync( + 'git', + ['tag', '--sort=-version:refname'], + { encoding: 'utf8' }, + ); + const lastTag = allTags + .split('\n') + .map((t) => t.trim()) + .filter((t) => /^v\d/.test(t))[0] || ''; + + if (lastTag) { + // Get commits since the last release + const recentCommits = execFileSync( + 'git', + ['log', '--oneline', `${lastTag}..HEAD`, '--', `packages/${extension}/`], + { encoding: 'utf8' }, + ).trim(); + if (recentCommits) { + const commits = recentCommits.split('\n').filter(Boolean); + commits.forEach((commit) => { + releaseNotes += `- ${commit}\n`; + }); + } else { + releaseNotes += '- General improvements and bug fixes\n'; + } + } else { + // First release - get all commits for this extension + const allCommits = execFileSync( + 'git', + ['log', '--oneline', '--', `packages/${extension}/`], + { encoding: 'utf8' }, + ).trim(); + if (allCommits) { + const commits = allCommits.split('\n').filter(Boolean); + commits.forEach((commit) => { + releaseNotes += `- ${commit}\n`; + }); + } else { + releaseNotes += '- Initial release\n'; + } + } + } catch (error) { + console.warn( + `Warning: Could not generate release notes for ${extension}:`, + error, + ); + releaseNotes += '- General improvements and bug fixes\n'; + } + + releaseNotes += '\n### Installation\n\n'; + releaseNotes += 'Download the VSIX file and install via:\n'; + releaseNotes += '- VS Code: Install from VSIX...\n'; + releaseNotes += '- Command line: `code --install-extension `\n'; + + if (preRelease === 'true') { + releaseNotes += '\n⚠️ **This is a pre-release version**\n'; + } + + if (isNightly === 'true') { + const nightlyDate = new Date() + .toISOString() + .split('T')[0] + .replace(/-/g, ''); + releaseNotes += `\n🌙 **This is a nightly build from ${nightlyDate}**\n`; + releaseNotes += '\n### Nightly Build Information\n'; + releaseNotes += `- **Build Date**: ${nightlyDate}\n`; + releaseNotes += `- **Version**: ${currentVersion} (pre-release build)\n`; + releaseNotes += '- **Type**: Nightly pre-release for testing\n'; + } + + return releaseNotes; +} + +function createGitHubRelease( + extension: string, + currentVersion: string, + releaseNotes: string, + vsixFiles: string[], + isNightly: string, + preRelease: string, + dryRun: boolean, +): void { + // Create release tag + let releaseTag = `v${currentVersion}`; + let releaseTitle = `${extension} v${currentVersion}`; + + // For nightly builds, add timestamp and branch to tag and title + if (isNightly === 'true') { + const nightlyDate = new Date() + .toISOString() + .split('T')[0] + .replace(/-/g, ''); + const branch = process.env.BRANCH || 'main'; + // Format branch name: main -> no suffix, tdx26/main -> .tdx26-main + const branchSuffix = + branch === 'main' ? '' : `.${branch.replace(/\//g, '-')}`; + releaseTag = `v${currentVersion}-nightly${branchSuffix}.${nightlyDate}`; + releaseTitle = `${extension} v${currentVersion} (Nightly ${branch} ${nightlyDate})`; + } + + if (dryRun) { + console.log('✅ DRY RUN: Would create GitHub release:'); + console.log(` - Tag: ${releaseTag}`); + console.log(` - Title: ${releaseTitle}`); + console.log(` - Pre-release: ${preRelease}`); + console.log(` - VSIX files: ${vsixFiles.join(', ')}`); + console.log(' - Release notes preview:'); + console.log(releaseNotes.split('\n').slice(0, 20).join('\n')); + console.log(' ... (truncated)'); + } else { + console.log('🔄 LIVE: Creating GitHub release...'); + console.log(`Creating release: ${releaseTitle}`); + console.log(`Tag: ${releaseTag}`); + console.log(`Pre-release: ${preRelease}`); + + try { + const repo = process.env.GITHUB_REPOSITORY || ''; + + // Check if release already exists (idempotency) + let releaseExists = false; + let hasAssets = false; + try { + const viewOutput = execFileSync( + 'gh', + ['release', 'view', releaseTag, '--repo', repo, '--json', 'assets'], + { encoding: 'utf8' }, + ); + const releaseData = JSON.parse(viewOutput); + releaseExists = true; + hasAssets = + Array.isArray(releaseData.assets) && releaseData.assets.length > 0; + } catch { + // Release does not exist — proceed to create + } + + if (releaseExists && hasAssets) { + console.log( + `⏭️ Release ${releaseTag} already exists with assets — skipping`, + ); + } else if (releaseExists) { + console.log( + `📎 Release ${releaseTag} exists but has no assets — uploading`, + ); + execFileSync( + 'gh', + ['release', 'upload', releaseTag, ...vsixFiles, '--repo', repo], + { stdio: 'inherit' }, + ); + console.log(`✅ Assets uploaded to existing release for ${extension}`); + } else { + // Write release notes to a temporary file to avoid shell escaping issues + const notesFile = join(process.cwd(), `.release-notes-${Date.now()}.tmp`); + try { + writeFileSync(notesFile, releaseNotes, 'utf8'); + } catch (writeError) { + console.error(`Failed to write release notes file: ${writeError}`); + throw writeError; + } + + const ghArgs = [ + 'release', + 'create', + releaseTag, + '--title', + releaseTitle, + '--notes-file', + notesFile, + ...(preRelease === 'true' ? ['--prerelease'] : []), + '--repo', + repo, + ...vsixFiles, + ]; + + try { + execFileSync('gh', ghArgs, { stdio: 'inherit' }); + + // Clean up notes file after successful creation + try { + unlinkSync(notesFile); + } catch (cleanupError) { + console.warn(`Warning: Failed to clean up notes file ${notesFile}: ${cleanupError}`); + } + console.log(`✅ Release created for ${extension}`); + } catch (createError) { + // Clean up notes file even on error + try { + unlinkSync(notesFile); + } catch (cleanupError) { + console.warn(`Warning: Failed to clean up notes file ${notesFile}: ${cleanupError}`); + } + throw createError; + } + } + } catch (error) { + console.error(`Failed to create release for ${extension}:`, error); + throw error; + } + } +} + +function createGitHubReleases(options: GitHubReleaseOptions): void { + const { + dryRun, + preRelease, + versionBump, + selectedExtensions, + isNightly, + vsixArtifactsPath, + } = options; + + console.log(`Mode: ${dryRun ? 'DRY RUN' : 'LIVE'}`); + console.log('Creating GitHub releases...'); + console.log(`Pre-release: ${preRelease}`); + console.log(`Version bump: ${versionBump}`); + console.log(`Extensions: ${selectedExtensions}`); + + const extensions = selectedExtensions.split(',').filter(Boolean); + + for (const ext of extensions) { + const packageDetails = getPackageDetails(ext); + if (!packageDetails) { + console.warn(`Skipping ${ext}: package.json not found`); + continue; + } + + console.log(`Processing extension: ${ext}`); + console.log(`Current version: ${packageDetails.version}`); + + const vsixFiles = findVsixFiles(ext, vsixArtifactsPath); + if (vsixFiles.length === 0) { + console.warn(`No VSIX files found for ${ext} in ${vsixArtifactsPath}`); + continue; + } + + const releaseNotes = generateReleaseNotes( + ext, + packageDetails.version, + isNightly, + preRelease, + ); + + createGitHubRelease( + ext, + packageDetails.version, + releaseNotes, + vsixFiles, + isNightly, + preRelease, + dryRun, + ); + } + + if (dryRun) { + console.log('✅ DRY RUN: GitHub release simulation completed'); + } else { + console.log('✅ LIVE: GitHub releases created'); + } +} + +// Export for use in other modules +export { createGitHubReleases }; diff --git a/packages/vscode-extension-ci/src/extension/ext-nightly-finder.ts b/packages/vscode-extension-ci/src/extension/ext-nightly-finder.ts new file mode 100644 index 00000000..441edd5b --- /dev/null +++ b/packages/vscode-extension-ci/src/extension/ext-nightly-finder.ts @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the + * repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import simpleGit from 'simple-git'; +import semver from 'semver'; +import { log, setOutput, extractVersionFromTag } from '../core/utils.js'; +import type { SemanticVersion } from '../core/types.js'; + +type SimpleGitType = ReturnType; + +export interface NightlyCandidate { + tag: string; + commitSha: string; + commitDate: number; + version: SemanticVersion; +} + +/** + * Nightly tag format: -v-nightly. + * or the legacy format: v-nightly. + * We match both by looking for "-nightly." in the tag name. + */ +function parseNightlyTag( + tagName: string, +): { version: SemanticVersion } | null { + if (!tagName.includes('-nightly.')) { + return null; + } + const version = extractVersionFromTag(tagName); + if (!version) { + return null; + } + return { version }; +} + +/** + * Check whether a tracking tag matching the given prefix exists in the tag list. + */ +function hasTrackingTag(allTagNames: Set, prefix: string): boolean { + for (const tag of allTagNames) { + if (tag.startsWith(prefix)) { + return true; + } + } + return false; +} + +/** + * Get all git tags with commit metadata. + */ +async function getAllTagsWithMeta( + git: SimpleGitType, +): Promise<{ name: string; commitSha: string; commitDate: number }[]> { + const tags = await git.tags(); + const result: { name: string; commitSha: string; commitDate: number }[] = []; + + for (const tagName of tags.all) { + try { + const logResult = await git.log({ + from: tagName, + to: tagName, + maxCount: 1, + }); + if (logResult.latest) { + const commitDate = + new Date(logResult.latest.date).getTime() / 1000; + result.push({ + name: tagName, + commitSha: logResult.latest.hash, + commitDate, + }); + } + } catch { + log.warning(`Failed to get metadata for tag ${tagName} — skipping`); + } + } + + // Newest first + return result.sort((a, b) => b.commitDate - a.commitDate); +} + +/** + * Find the best nightly build eligible for promotion to pre-release. + * + * Filters applied (all must pass): + * 1. Tag format must match nightly pattern (contains "-nightly.") + * 2. Tag must be at least MIN_TAG_AGE_DAYS days old (default 7) + * 3. No existing marketplace-prerelease-* tracking tag for this version + * (nightly was already promoted to pre-release) + * 4. Floor check: no marketplace-stable-* tag for the derived stable version + * semver.inc(nightlyVersion, 'minor') — prevents re-promoting a version + * track that was already published as stable + * + * Returns the newest passing candidate. + */ +export async function findNightlyCandidate(): Promise { + const minAgeDays = parseInt(process.env.MIN_TAG_AGE_DAYS ?? '7', 10); + const minAgeSeconds = minAgeDays * 24 * 60 * 60; + const now = Math.floor(Date.now() / 1000); + + const extensionId = process.env.EXTENSION_ID; + if (!extensionId) { + throw new Error('EXTENSION_ID env var is required'); + } + const tagPrefix = process.env.TAG_PREFIX || 'marketplace'; + + log.info(`Finding nightly candidate (min age: ${minAgeDays} days)...`); + + const git: SimpleGitType = simpleGit(); + + const allTagsWithMeta = await getAllTagsWithMeta(git); + const allTagNames = new Set(allTagsWithMeta.map((t) => t.name)); + + const candidates: NightlyCandidate[] = []; + + for (const { name, commitSha, commitDate } of allTagsWithMeta) { + const parsed = parseNightlyTag(name); + if (!parsed) { + continue; + } + const { version } = parsed; + + // Filter 1: minimum age + const ageSeconds = now - commitDate; + if (ageSeconds < minAgeSeconds) { + log.debug( + `Skipping ${name}: too recent (${Math.floor(ageSeconds / 86400)} days old, need ${minAgeDays})`, + ); + continue; + } + + // Filter 2: not already promoted to pre-release + const versionSpecificPrefix = `${tagPrefix}-prerelease-${extensionId}-v${version}`; + if (hasTrackingTag(allTagNames, versionSpecificPrefix)) { + log.debug( + `Skipping ${name}: already has ${tagPrefix}-prerelease tracking tag for v${version}`, + ); + continue; + } + + // Filter 3: floor check — derived stable version not already published + const derivedStable = semver.inc(version, 'minor'); + if (derivedStable) { + const stableTrackingPrefix = `${tagPrefix}-stable-${extensionId}-v${derivedStable}`; + if (hasTrackingTag(allTagNames, stableTrackingPrefix)) { + log.debug( + `Skipping ${name}: derived stable v${derivedStable} already published`, + ); + continue; + } + } + + candidates.push({ tag: name, commitSha, commitDate, version }); + } + + if (candidates.length === 0) { + log.warning('No eligible nightly candidates found'); + return null; + } + + // Already sorted newest-first; pick first + const best = candidates[0]; + log.success(`Selected nightly candidate: ${best.tag}`); + log.info(` Commit SHA: ${best.commitSha}`); + log.info(` Version: ${best.version}`); + log.info( + ` Age: ${Math.floor((now - best.commitDate) / 86400)} days`, + ); + + return best; +} + +/** + * Set GitHub Actions outputs for the nightly candidate. + * Outputs commit-sha and nightly-tag (empty strings if no candidate). + */ +export function setNightlyFinderOutputs( + candidate: NightlyCandidate | null, +): void { + setOutput('commit-sha', candidate?.commitSha ?? ''); + setOutput('nightly-tag', candidate?.tag ?? ''); + log.success('Nightly finder outputs set'); +} + +/** + * Main function for CLI usage via index.ts. + */ +export async function main(): Promise { + try { + const candidate = await findNightlyCandidate(); + setNightlyFinderOutputs(candidate); + } catch (error) { + log.error(`Failed to find nightly candidate: ${error}`); + process.exit(1); + } +} + +if (import.meta.url === `file://${process.argv[1]}`) { + main(); +} diff --git a/packages/vscode-extension-ci/src/extension/ext-package-selector.ts b/packages/vscode-extension-ci/src/extension/ext-package-selector.ts new file mode 100644 index 00000000..bc7e4214 --- /dev/null +++ b/packages/vscode-extension-ci/src/extension/ext-package-selector.ts @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the + * repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { readdirSync, existsSync } from 'fs'; +import { join } from 'path'; +import { log, setOutput, getExtensionInfo } from '../core/utils.js'; + +/** + * Get all available VS Code extensions + */ +export function getAvailableExtensions(): string { + log.info('Getting all available VS Code extensions...'); + + // Get all packages from the packages directory (configurable via PACKAGES_ROOT) + const packagesRoot = process.env.PACKAGES_ROOT || 'packages'; + const packagesDir = join(process.cwd(), packagesRoot); + const extensions: string[] = []; + + if (!existsSync(packagesDir)) { + log.warning('packages directory not found'); + return '[]'; + } + + const packageDirs = readdirSync(packagesDir, { withFileTypes: true }) + .filter((dirent) => dirent.isDirectory()) + .map((dirent) => dirent.name); + + for (const packageName of packageDirs) { + const packagePath = join(packagesDir, packageName); + const packageJsonPath = join(packagePath, 'package.json'); + + if (existsSync(packageJsonPath)) { + try { + const info = getExtensionInfo(packagePath); + + // Only include packages that have a publisher (VS Code extensions) + if (info.publisher) { + extensions.push(packageName); + log.debug( + `Found VS Code extension: ${packageName} (publisher: ${info.publisher})`, + ); + } else { + log.debug(`Skipping NPM package: ${packageName} (no publisher)`); + } + } catch (error) { + log.warning(`Failed to read package.json for ${packageName}: ${error}`); + } + } + } + + const jsonArray = JSON.stringify(extensions); + log.info( + `Found ${extensions.length} VS Code extensions: ${extensions.join(', ')}`, + ); + log.debug(`JSON array: ${jsonArray}`); + + return jsonArray; +} + +/** + * Set GitHub Actions outputs for extension discovery + */ +export function setExtensionDiscoveryOutputs(extensions: string): void { + setOutput('extensions', extensions); + + log.success('Extension discovery outputs set'); +} + +/** + * Main function for CLI usage + */ +export async function main(): Promise { + try { + const extensions = getAvailableExtensions(); + setExtensionDiscoveryOutputs(extensions); + } catch (error) { + log.error(`Failed to discover extensions: ${error}`); + process.exit(1); + } +} + +// Run if called directly +if (import.meta.url === `file://${process.argv[1]}`) { + main(); +} diff --git a/packages/vscode-extension-ci/src/extension/ext-publish-matrix.ts b/packages/vscode-extension-ci/src/extension/ext-publish-matrix.ts new file mode 100644 index 00000000..9a13cf64 --- /dev/null +++ b/packages/vscode-extension-ci/src/extension/ext-publish-matrix.ts @@ -0,0 +1,171 @@ +#!/usr/bin/env tsx +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the + * repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { log } from '../core/utils.js'; +import { readdirSync, existsSync, readFileSync } from 'fs'; +import { join } from 'path'; + +interface PublishMatrixEntry { + registry: string; + vsix_pattern: string; + marketplace: string; +} + +interface PublishMatrixOptions { + registries: string; + selectedExtensions: string; +} + +/** + * Get all available VS Code extensions (packages with publisher field) + */ +function getAvailableExtensions(): string[] { + const extensions: string[] = []; + const packagesDir = join(process.cwd(), 'packages'); + + if (!existsSync(packagesDir)) { + log.warning('packages directory not found'); + return extensions; + } + + const packageDirs = readdirSync(packagesDir, { withFileTypes: true }) + .filter((dirent) => dirent.isDirectory()) + .map((dirent) => dirent.name); + + for (const packageName of packageDirs) { + const packagePath = join(packagesDir, packageName); + const packageJsonPath = join(packagePath, 'package.json'); + + if (existsSync(packageJsonPath)) { + try { + const packageJson = JSON.parse( + readFileSync(packageJsonPath, 'utf-8'), + ); + + // Only include packages that have a publisher (VS Code extensions) + if (packageJson.publisher) { + extensions.push(packageName); + log.debug( + `Found VS Code extension: ${packageName} (publisher: ${packageJson.publisher})`, + ); + } else { + log.debug(`Skipping NPM package: ${packageName} (no publisher)`); + } + } catch (error) { + log.warning(`Failed to read package.json for ${packageName}: ${error}`); + } + } + } + + return extensions; +} + +function getVsixPattern(extension: string): string { + const vsixGlob = process.env.VSIX_GLOB; + if (!vsixGlob) { + throw new Error('VSIX_GLOB env var is required'); + } + + // VSIX_GLOB is either a single glob (applies to all extensions) + // or a JSON map of extension name → glob. + if (vsixGlob.trim().startsWith('{')) { + let map: Record; + try { + map = JSON.parse(vsixGlob); + } catch (err) { + throw new Error( + `VSIX_GLOB looks like JSON but failed to parse: ${(err as Error).message}`, + ); + } + const pattern = map[extension]; + if (!pattern) { + throw new Error( + `VSIX_GLOB map has no entry for extension '${extension}'. Available keys: ${Object.keys(map).join(', ')}`, + ); + } + return pattern; + } + + return vsixGlob; +} + +function getMarketplaceName(registry: string): string { + switch (registry) { + case 'vsce': + return 'VS Code Marketplace'; + case 'ovsx': + return 'Open VSX Registry'; + default: + return registry; + } +} + +function determinePublishMatrix( + options: PublishMatrixOptions, +): PublishMatrixEntry[] { + const { registries, selectedExtensions } = options; + + // Handle special values and empty/undefined selectedExtensions + if (!selectedExtensions || selectedExtensions.trim() === '') { + log.info('No extensions selected for publishing, returning empty matrix'); + return []; + } + + // Handle special values + const normalizedSelection = selectedExtensions.trim().toLowerCase(); + if (normalizedSelection === 'none') { + log.info('Extensions set to "none" - returning empty matrix'); + return []; + } + + // Determine which extensions to include + let extensions: string[]; + if (normalizedSelection === 'all') { + log.info('Extensions set to "all" - including all available extensions'); + extensions = getAvailableExtensions(); + } else { + // Parse comma-separated list of specific extensions + extensions = selectedExtensions.split(',').filter(Boolean); + } + + if (extensions.length === 0) { + log.info('No extensions to publish, returning empty matrix'); + return []; + } + + // Determine which registries to include + const registryList = + registries === 'all' + ? ['vsce', 'ovsx'] + : registries.split(',').filter(Boolean); + + // Create matrix entries for each extension-registry combination + const matrix: PublishMatrixEntry[] = []; + + for (const ext of extensions) { + if (!ext) continue; + + const vsixPattern = getVsixPattern(ext); + + for (const registry of registryList) { + const marketplace = getMarketplaceName(registry); + + matrix.push({ + registry, + vsix_pattern: vsixPattern, + marketplace, + }); + } + } + log.info(`Publish matrix: ${JSON.stringify(matrix, null, 2)}`); + return matrix; +} + +// Export for use in other modules +export { determinePublishMatrix }; diff --git a/packages/vscode-extension-ci/src/extension/ext-release-plan.ts b/packages/vscode-extension-ci/src/extension/ext-release-plan.ts new file mode 100644 index 00000000..7b432ec0 --- /dev/null +++ b/packages/vscode-extension-ci/src/extension/ext-release-plan.ts @@ -0,0 +1,167 @@ +#!/usr/bin/env tsx +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the + * repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +/** + * Extension Release Plan Display Script + * + * This script displays a detailed release plan for VS Code extensions during dry runs. + * It shows what would happen for each extension including version bumps, release creation, + * and marketplace publishing. + */ + +import { readFileSync } from 'fs'; +import { join } from 'path'; + +interface PackageJson { + name: string; + version: string; + publisher?: string; + displayName?: string; +} + +interface ReleasePlanOptions { + branch?: string; + buildType: string; + isNightly: string; + versionBump: string; + registries: string; + preRelease: string; + selectedExtensions: string; +} + +function parseVersion(version: string): { + major: number; + minor: number; + patch: number; +} { + const [major, minor, patch] = version.split('.').map(Number); + return { major, minor, patch }; +} + +function calculateNewVersion( + currentVersion: string, + versionBump: string, +): string { + const { major, minor, patch } = parseVersion(currentVersion); + + switch (versionBump) { + case 'major': + return `${major + 1}.0.0`; + case 'minor': + return `${major}.${minor + 1}.0`; + case 'patch': + return `${major}.${minor}.${patch + 1}`; + case 'auto': + default: + return `${major}.${minor}.${patch + 1}`; + } +} + +function getPackageDetails(extensionPath: string): PackageJson | null { + try { + const packageJsonPath = join( + process.cwd(), + 'packages', + extensionPath, + 'package.json', + ); + const content = readFileSync(packageJsonPath, 'utf-8'); + return JSON.parse(content); + } catch (error) { + console.warn( + `Warning: Could not read package.json for ${extensionPath}:`, + error, + ); + return null; + } +} + +function displayReleasePlan(options: ReleasePlanOptions): void { + const { + branch = 'main', + buildType, + isNightly, + versionBump, + registries, + preRelease, + selectedExtensions, + } = options; + + console.log('=== EXTENSION RELEASE PLAN ==='); + console.log(`Branch: ${branch}`); + console.log(`Build type: ${buildType}`); + console.log(`Is nightly: ${isNightly}`); + console.log(`Version bump type: ${versionBump}`); + console.log(`Registries: ${registries}`); + console.log(`Pre-release: ${preRelease}`); + console.log('Dry run mode: ENABLED'); + console.log(''); + + console.log(`Extensions to release: ${selectedExtensions}`); + console.log(''); + + const extensions = selectedExtensions.split(',').filter(Boolean); + + for (const ext of extensions) { + const packageDetails = getPackageDetails(ext); + if (!packageDetails) { + console.log(`Extension: ${ext} (package.json not found)`); + continue; + } + + console.log(`Extension: ${ext}`); + console.log(` Current version: ${packageDetails.version}`); + console.log(` Publisher: ${packageDetails.publisher || 'N/A'}`); + + const newVersion = calculateNewVersion(packageDetails.version, versionBump); + console.log(` Would bump to: ${newVersion}`); + + if (isNightly === 'true') { + console.log( + ' Version strategy: Nightly build (odd minor + nightly timestamp)', + ); + } else { + console.log( + ` Version strategy: ${versionBump} (conventional commit) + VS Code even/odd (pre-release: ${preRelease})`, + ); + } + + const preReleaseText = preRelease === 'true' ? ' (pre-release)' : ''; + console.log(` Would create GitHub release: ${ext}${preReleaseText}`); + + // Determine which registries to include (same logic as ext-publish-matrix.ts) + const registryList = + registries === 'all' + ? ['vsce', 'ovsx'] + : registries.split(',').filter(Boolean); + + // Show publishing destinations for each registry + for (const registry of registryList) { + switch (registry) { + case 'vsce': + console.log( + ` Would publish to: VSCode Marketplace${preReleaseText}`, + ); + break; + case 'ovsx': + console.log(` Would publish to: Open VSX Registry${preReleaseText}`); + break; + default: + console.log(` Would publish to: ${registry}${preReleaseText}`); + break; + } + } + console.log(''); + } + + console.log('✅ Extension release dry run completed'); +} + +// Export for use in other modules +export { displayReleasePlan as displayExtensionReleasePlan }; diff --git a/packages/vscode-extension-ci/src/extension/ext-version-bumper.ts b/packages/vscode-extension-ci/src/extension/ext-version-bumper.ts new file mode 100644 index 00000000..52e39f64 --- /dev/null +++ b/packages/vscode-extension-ci/src/extension/ext-version-bumper.ts @@ -0,0 +1,375 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the + * repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { execFileSync } from 'child_process'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +interface PackageJson { + name: string; + version: string; + publisher?: string; + displayName?: string; +} + +interface VersionBumpOptions { + selectedExtensions: string; + preRelease: string; + isNightly: string; + extensionId?: string; + newMajor?: string; +} + +export type { VersionBumpOptions }; + +interface ParsedSemver { + semver: string; + major: number; + minor: number; + patch: number; +} + +const isCI = (): boolean => process.env.CI === 'true'; + +const errorAndExit = (msg: string): never => { + const prefix = isCI() ? '::error::' : '\x1b[31m[Error]\x1b[0m '; + console.log(`${prefix}${msg}`); + process.exit(isCI() ? 1 : 0); +}; + +const validateNewMajor = (rawValue?: string): number | undefined => { + const major = rawValue ?? process.env.NEW_MAJOR; + if (!major) return undefined; + if (major.includes('.') || isNaN(parseInt(major, 10))) { + errorAndExit(`Invalid NEW_MAJOR value (${major}). Must be a whole number`); + } + return parseInt(major, 10); +}; + +const parseSemver = (version: string): ParsedSemver => { + const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/); + if (!match) { + errorAndExit(`Invalid version format: ${version}`); + // errorAndExit normally process.exits, so this return is only reached + // when the exit is mocked (in tests). Return a sentinel that callers + // shouldn't rely on; production never sees this path. + return { semver: version, major: 0, minor: 0, patch: 0 }; + } + const [semver, major, minor, patch, prerelease] = match; + if (prerelease) { + errorAndExit( + 'Prerelease versions (e.g. 1.2.3-beta.0) are not currently supported in the VSCode Marketplace', + ); + return { semver: version, major: 0, minor: 0, patch: 0 }; + } + return { + semver, + major: parseInt(major, 10), + minor: parseInt(minor, 10), + patch: parseInt(patch, 10), + }; +}; + +/** + * Query the VS Code Marketplace for the highest pre-release version of an extension. + * Returns null if no pre-release has ever been published (bootstrap case). + * Throws if vsce show fails or returns malformed data. + */ +const getLatestPreReleaseVersionFromMarketplace = ( + extensionId: string, +): string | null => { + let versionsJson: string; + try { + versionsJson = execFileSync('npx', [ + '@vscode/vsce', + 'show', + extensionId, + '--json', + ]) + .toString() + .trim(); + } catch (error) { + throw new Error( + `Failed to query marketplace for ${extensionId}: ${(error as Error).message}`, + ); + } + + if (versionsJson === 'undefined') { + throw new Error( + `No version info found for ${extensionId}. Run 'npx @vscode/vsce show ${extensionId} --json' locally to debug.`, + ); + } + + const parsed = JSON.parse(versionsJson); + const preReleaseVersions = parsed.versions.filter((version: any) => + version.properties?.some( + (prop: any) => + prop.key === 'Microsoft.VisualStudio.Code.PreRelease' && + prop.value === 'true', + ), + ); + + if (preReleaseVersions.length === 0) { + console.log( + `No pre-release versions found for ${extensionId}. Treating as bootstrap (first prerelease).`, + ); + return null; + } + + // vsce returns versions ordered by lastUpdated descending. + return preReleaseVersions[0].version; +}; + +/** + * Decide the next version based on: + * - main: the version in main's package.json (what we're building from) + * - marketplace: the latest prerelease in the marketplace, or null if none yet + * - newMajor: optional major override (engineer-driven) + * + * Decision tree: + * 1. newMajor passed → validate (must be main.major + 1, unless FORCE_NEW_MAJOR=true) → return ${newMajor}.0.0 + * 2. marketplace is null (bootstrap) → bump main.minor by 1, reset patch + * 3. main matches marketplace → "promotions just ran" → bump main.minor by 1, reset patch + * 4. main minor ahead of marketplace → bump main.patch + * 5. main major ahead of marketplace → bump main.patch + * 6. Otherwise (main < marketplace) → throw with diagnostic + */ +export const buildNextVersion = ( + main: ParsedSemver, + marketplace: ParsedSemver | null, + newMajor?: number, +): string => { + if (newMajor !== undefined) { + if (process.env.FORCE_NEW_MAJOR === 'true') { + console.log( + `::warning::FORCE_NEW_MAJOR is set to true. Bypassing new major version checks.`, + ); + } else { + if (marketplace !== null && main.major !== marketplace.major) { + errorAndExit( + `A new major was passed (${newMajor}), however the major versions in 'main' (${main.semver}) and the 'marketplace' (${marketplace.semver}) already do NOT match. This suggests that a new major was just recently published as a pre-release in the marketplace. Please confirm versions in main and in the marketplace.`, + ); + } + if (newMajor - main.major !== 1) { + errorAndExit( + `The new major version (${newMajor}) is not exactly 1 greater than the current major version in 'main' (${main.major}). Please confirm the correct new major version.`, + ); + } + console.log(`New major version passed validation: ${newMajor}`); + } + console.log(`::warning::Setting new major version to ${newMajor}.0.0`); + return `${newMajor}.0.0`; + } + + if (marketplace === null) { + console.log( + `Bootstrap: no marketplace prerelease yet. Bumping minor from main (${main.semver}).`, + ); + return `${main.major}.${main.minor + 1}.0`; + } + + if (main.semver === marketplace.semver) { + console.log( + `Versions match (${main.semver}). Promotions likely just ran, bumping MINOR.`, + ); + return `${main.major}.${main.minor + 1}.0`; + } + + if (main.major === marketplace.major && main.minor > marketplace.minor) { + console.log( + `Majors match and main minor is greater (${main.semver} > ${marketplace.semver}). Nightly already ahead, bumping PATCH.`, + ); + return `${main.major}.${main.minor}.${main.patch + 1}`; + } + + if (main.major > marketplace.major) { + console.log( + `Main major already ahead (${main.semver} > ${marketplace.semver}). Bumping PATCH.`, + ); + return `${main.major}.${main.minor}.${main.patch + 1}`; + } + + throw new Error( + `Cannot determine next version: main (${main.semver}) is behind marketplace prerelease (${marketplace.semver}). ` + + `This shouldn't happen — main should always be ahead of or equal to the latest prerelease. ` + + `Check whether main was reverted or whether the marketplace lookup is returning a stale value.`, + ); +}; + +const getPackageDetails = (extensionPath: string): PackageJson | null => { + try { + const packageJsonPath = join( + process.cwd(), + 'packages', + extensionPath, + 'package.json', + ); + const content = readFileSync(packageJsonPath, 'utf-8'); + return JSON.parse(content); + } catch (error) { + console.warn( + `Warning: Could not read package.json for ${extensionPath}:`, + error, + ); + return null; + } +}; + +const createGitTag = ( + packageName: string, + version: string, + isPreRelease: boolean, + isNightly: boolean, +): void => { + let tagName: string; + if (isNightly) { + const nightlyDate = new Date() + .toISOString() + .split('T')[0] + .replace(/-/g, ''); + const branch = process.env.BRANCH || 'main'; + const branchSuffix = + branch === 'main' ? '' : `.${branch.replace(/\//g, '-')}`; + tagName = `v${version}-nightly${branchSuffix}.${nightlyDate}`; + } else { + tagName = isPreRelease + ? `${packageName}-v${version}-pre-release` + : `${packageName}-v${version}`; + } + + try { + let tagExists = false; + try { + execFileSync('git', ['rev-parse', tagName], { + encoding: 'utf8', + stdio: 'pipe', + }); + tagExists = true; + } catch { + try { + execFileSync('git', ['ls-remote', '--tags', 'origin', tagName], { + encoding: 'utf8', + stdio: 'pipe', + }); + tagExists = true; + } catch { + tagExists = false; + } + } + + if (tagExists) { + console.log( + `⏭️ Tag ${tagName} already exists — skipping (idempotent rerun)`, + ); + return; + } + + console.log(`Creating tag ${tagName} on current commit...`); + execFileSync('git', ['tag', tagName], { stdio: 'inherit' }); + console.log(`✅ Tag created: ${tagName}`); + } catch (error) { + console.error(`Failed to create tag ${tagName}:`, error); + throw error; + } +}; + +/** + * Public entry point. Iterates over selected extensions, decides the next + * version using marketplace-lookup, runs `npm version`, and creates git tags. + */ +export const bumpVersions = (options: VersionBumpOptions): void => { + const { + selectedExtensions, + preRelease, + isNightly, + extensionId, + newMajor: newMajorRaw, + } = options; + + console.log(`Selected extensions: ${selectedExtensions}`); + console.log(`Pre-release mode: ${preRelease}`); + console.log(`Is nightly build: ${isNightly}`); + console.log( + `Extension ID for marketplace lookup: ${extensionId || '(not set)'}`, + ); + console.log(`New major override: ${newMajorRaw || '(not set)'}`); + + if (!extensionId) { + errorAndExit( + 'EXTENSION_ID env var is required for marketplace-lookup version selection. ' + + 'Pass the marketplace extension id (e.g. salesforce.salesforcedx-vscode).', + ); + } + + const newMajor = validateNewMajor(newMajorRaw); + + const extensions = selectedExtensions.split(',').filter(Boolean); + + for (const ext of extensions) { + const packageDetails = getPackageDetails(ext); + if (!packageDetails) { + console.warn(`Skipping ${ext}: package.json not found`); + continue; + } + + console.log(`\nProcessing ${ext}...`); + console.log( + `Current version (from package.json on main): ${packageDetails.version}`, + ); + + const main = parseSemver(packageDetails.version); + const marketplaceVersion = + newMajor !== undefined + ? null + : getLatestPreReleaseVersionFromMarketplace(extensionId!); + const marketplace = marketplaceVersion + ? parseSemver(marketplaceVersion) + : null; + + if (marketplace) { + console.log(`Marketplace prerelease: ${marketplace.semver}`); + } + + const newVersion = buildNextVersion(main, marketplace, newMajor); + + console.log( + `🔄 Bumping ${ext} from ${packageDetails.version} to ${newVersion}`, + ); + + const originalDir = process.cwd(); + try { + process.chdir(join(originalDir, 'packages', ext)); + execFileSync('npm', ['version', newVersion, '--no-git-tag-version'], { + stdio: 'inherit', + }); + process.chdir(originalDir); + + createGitTag( + packageDetails.name, + newVersion, + preRelease === 'true', + isNightly === 'true', + ); + } catch (error) { + console.error(`Failed to bump version for ${ext}:`, error); + process.chdir(originalDir); + throw error; + } + } + + console.log('\n✅ Version bumps and tags applied'); +}; + +// Exported for testability of internal helpers. +export { + isCI, + errorAndExit, + validateNewMajor, + parseSemver, + getLatestPreReleaseVersionFromMarketplace, +}; diff --git a/packages/vscode-extension-ci/src/index.ts b/packages/vscode-extension-ci/src/index.ts new file mode 100644 index 00000000..e51351e5 --- /dev/null +++ b/packages/vscode-extension-ci/src/index.ts @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE file in the + * repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +// Core utilities and types +export * from './core/types.js'; +export * from './core/utils.js'; +export * from './core/audit-logger.js'; + +// Extension management +export { determineBuildType, setBuildTypeOutputs } from './extension/ext-build-type.js'; +export { detectExtensionChanges, setChangeDetectionOutputs } from './extension/ext-change-detector.js'; +export { createGitHubReleases } from './extension/ext-github-releases.js'; +export { findNightlyCandidate, setNightlyFinderOutputs } from './extension/ext-nightly-finder.js'; +export { getAvailableExtensions, setExtensionDiscoveryOutputs } from './extension/ext-package-selector.js'; +export { determinePublishMatrix } from './extension/ext-publish-matrix.js'; +export { displayExtensionReleasePlan } from './extension/ext-release-plan.js'; +export { bumpVersions } from './extension/ext-version-bumper.js'; + +// NPM package management +export { detectNpmChanges, setNpmChangeDetectionOutputs } from './npm/npm-change-detector.js'; +export { extractPackageDetails, setPackageDetailsOutputs } from './npm/npm-package-details.js'; +export { npmPackageSelectorMain } from './npm/npm-package-selector.js'; +export { generateReleasePlan, displayReleasePlan } from './npm/npm-release-plan.js'; diff --git a/packages/vscode-extension-ci/src/npm/npm-change-detector.ts b/packages/vscode-extension-ci/src/npm/npm-change-detector.ts new file mode 100644 index 00000000..b2b904cc --- /dev/null +++ b/packages/vscode-extension-ci/src/npm/npm-change-detector.ts @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the + * repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { simpleGit } from 'simple-git'; +import { readdirSync, existsSync } from 'fs'; +import { join } from 'path'; +import { + NpmPackageInfo, + NpmChangeDetectionResult, + VersionBumpType, +} from './npm-types.js'; +import { log, setOutput, getExtensionInfo } from '../core/utils.js'; + +/** + * Get all available NPM packages (packages without publisher field) + */ +function getAvailableNpmPackages(): NpmPackageInfo[] { + const packages: NpmPackageInfo[] = []; + const packagesRoot = process.env.PACKAGES_ROOT || 'packages'; + const packagesDir = join(process.cwd(), packagesRoot); + + if (!existsSync(packagesDir)) { + log.warning('packages directory not found'); + return packages; + } + + const packageDirs = readdirSync(packagesDir, { withFileTypes: true }) + .filter((dirent) => dirent.isDirectory()) + .map((dirent) => dirent.name); + + for (const packageName of packageDirs) { + const packagePath = join(packagesDir, packageName); + const packageJsonPath = join(packagePath, 'package.json'); + + if (existsSync(packageJsonPath)) { + try { + const info = getExtensionInfo(packagePath); + + // Only include packages that don't have a publisher (NPM packages) + if (!info.publisher) { + packages.push({ + name: packageName, + path: packagePath, + currentVersion: info.version, + description: info.displayName, + isExtension: false, + }); + log.debug(`Found NPM package: ${packageName}`); + } else { + log.debug( + `Skipping VS Code extension: ${packageName} (publisher: ${info.publisher})`, + ); + } + } catch (error) { + log.warning(`Failed to read package.json for ${packageName}: ${error}`); + } + } + } + + return packages; +} + +/** + * Check if package has changes since base branch + */ +async function hasPackageChanges( + git: any, + packagePath: string, + baseBranch: string, +): Promise { + try { + // Check for changes since the base branch + const diff = await git.diff([ + `origin/${baseBranch}`, + 'HEAD', + '--', + packagePath, + ]); + return diff.trim().length > 0; + } catch (error) { + log.warning(`Failed to check changes for ${packagePath}: ${error}`); + return false; + } +} + +/** + * Determine version bump type from commit messages + */ +async function determineVersionBump(git: any): Promise { + try { + const logResult = await git.log({ maxCount: 5 }); + const commitMessages = logResult.all + .map((commit: any) => commit.message) + .join('\n'); + + log.debug('Analyzing commit messages for version bump:'); + log.debug(commitMessages); + + if ( + commitMessages.toLowerCase().includes('breaking') || + commitMessages.toLowerCase().includes('major') + ) { + log.info('Found breaking change - using major bump'); + return 'major'; + } else if ( + commitMessages.toLowerCase().includes('feat') || + commitMessages.toLowerCase().includes('feature') || + commitMessages.toLowerCase().includes('minor') + ) { + log.info('Found feature - using minor bump'); + return 'minor'; + } else { + log.info('No breaking changes or features found - using patch bump'); + return 'patch'; + } + } catch (error) { + log.warning( + `Failed to determine version bump: ${error}, defaulting to patch`, + ); + return 'patch'; + } +} + +/** + * Detect changes in NPM packages + */ +export async function detectNpmChanges( + baseBranch: string = 'main', +): Promise { + log.info('Detecting changes in NPM packages...'); + log.debug(`Base branch: ${baseBranch}`); + + const git = simpleGit(); + + // Verify base branch exists + try { + const branches = await git.branch(['-r']); + const baseBranchExists = branches.all.some((branch: string) => + branch.includes(`origin/${baseBranch}`), + ); + + if (!baseBranchExists) { + log.warning( + `Base branch 'origin/${baseBranch}' does not exist, falling back to 'main'`, + ); + baseBranch = 'main'; + } + } catch (error) { + log.warning(`Failed to check base branch: ${error}, using 'main'`); + baseBranch = 'main'; + } + + // Get all NPM packages (packages without publisher field) + const npmPackages = getAvailableNpmPackages(); + + log.info( + `Found ${npmPackages.length} NPM packages: ${npmPackages.map((p) => p.name).join(', ')}`, + ); + + // Check for changes in each package + const changedPackages: string[] = []; + + for (const pkg of npmPackages) { + log.debug(`Checking package: ${pkg.name}`); + + const hasChanges = await hasPackageChanges(git, pkg.path, baseBranch); + + if (hasChanges) { + log.info(`Found changes in ${pkg.name} - including in release`); + changedPackages.push(pkg.name); + } else { + log.info(`No changes found in ${pkg.name} - skipping release`); + } + } + + // Determine version bump type + const versionBump = await determineVersionBump(git); + + log.info(`Changed packages: ${changedPackages.join(', ')}`); + log.info(`Version bump type: ${versionBump}`); + + return { + changedPackages, + selectedPackages: [], // Will be set by package selector + versionBump, + }; +} + +/** + * Set GitHub Actions outputs for NPM change detection + */ +export function setNpmChangeDetectionOutputs( + result: NpmChangeDetectionResult, +): void { + setOutput('packages', result.changedPackages.join(',')); + setOutput('bump', result.versionBump); + + log.success('NPM change detection outputs set'); +} + +/** + * Main function for CLI usage + */ +export async function main(): Promise { + try { + const baseBranch = process.env.INPUT_BASE_BRANCH || 'main'; + const result = await detectNpmChanges(baseBranch); + setNpmChangeDetectionOutputs(result); + } catch (error) { + log.error(`Failed to detect NPM changes: ${error}`); + process.exit(1); + } +} + +// Run if called directly +if (import.meta.url === `file://${process.argv[1]}`) { + main(); +} diff --git a/packages/vscode-extension-ci/src/npm/npm-package-details.ts b/packages/vscode-extension-ci/src/npm/npm-package-details.ts new file mode 100644 index 00000000..09e556b7 --- /dev/null +++ b/packages/vscode-extension-ci/src/npm/npm-package-details.ts @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the + * repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { readFileSync, existsSync } from 'fs'; +import { join } from 'path'; +import { NpmPackageDetails, VersionBumpType } from './npm-types.js'; +import { log, setOutput } from '../core/utils.js'; + +/** + * Parse package.json and extract details + */ +function getPackageDetails(packageName: string): { + name: string; + version: string; + description: string; +} | null { + const packagePath = join(process.cwd(), 'packages', packageName); + const packageJsonPath = join(packagePath, 'package.json'); + + if (!existsSync(packageJsonPath)) { + log.warning(`package.json not found for ${packageName}`); + return null; + } + + try { + const content = readFileSync(packageJsonPath, 'utf-8'); + const pkg = JSON.parse(content); + + return { + name: pkg.name || packageName, + version: pkg.version || '0.0.0', + description: pkg.description || 'No description', + }; + } catch (error) { + log.warning(`Failed to parse package.json for ${packageName}: ${error}`); + return null; + } +} + +/** + * Extract package details from JSON array string + */ +export function extractPackageDetails( + selectedPackagesJson: string, + versionBump: VersionBumpType, +): NpmPackageDetails { + log.info('Extracting package details...'); + log.debug(`Selected packages JSON: ${selectedPackagesJson}`); + log.debug(`Version bump: ${versionBump}`); + + const packageNames: string[] = []; + const packageVersions: string[] = []; + const packageDescriptions: string[] = []; + + try { + // Parse the JSON array of selected packages + if (selectedPackagesJson && selectedPackagesJson !== '[]') { + const packages = JSON.parse(selectedPackagesJson); + + if (Array.isArray(packages)) { + for (const packageName of packages) { + if (packageName && typeof packageName === 'string') { + const details = getPackageDetails(packageName); + + if (details) { + packageNames.push(details.name); + packageVersions.push(details.version); + packageDescriptions.push(details.description); + + log.debug( + `Package ${packageName}: ${details.name}@${details.version}`, + ); + } + } + } + } + } + } catch (error) { + log.error(`Failed to parse selected packages JSON: ${error}`); + } + + log.info(`Extracted details for ${packageNames.length} packages`); + log.info(`Package names: ${packageNames.join(', ')}`); + log.info(`Package versions: ${packageVersions.join(', ')}`); + + return { + packageNames, + packageVersions, + packageDescriptions, + versionBump, + }; +} + +/** + * Set GitHub Actions outputs for package details + */ +export function setPackageDetailsOutputs(details: NpmPackageDetails): void { + setOutput('package_names', details.packageNames.join(', ')); + setOutput('package_versions', details.packageVersions.join(', ')); + setOutput('package_descriptions', details.packageDescriptions.join(', ')); + setOutput('version_bump', details.versionBump); + + log.success('Package details outputs set'); +} + +/** + * Main function for CLI usage + */ +export async function main(): Promise { + try { + const selectedPackagesJson = process.env.SELECTED_PACKAGES || '[]'; + const versionBump = + (process.env.VERSION_BUMP as VersionBumpType) || 'patch'; + + const details = extractPackageDetails(selectedPackagesJson, versionBump); + setPackageDetailsOutputs(details); + } catch (error) { + log.error(`Failed to extract package details: ${error}`); + process.exit(1); + } +} + +// Run if called directly +if (import.meta.url === `file://${process.argv[1]}`) { + main(); +} diff --git a/packages/vscode-extension-ci/src/npm/npm-package-selector.ts b/packages/vscode-extension-ci/src/npm/npm-package-selector.ts new file mode 100644 index 00000000..a8a57301 --- /dev/null +++ b/packages/vscode-extension-ci/src/npm/npm-package-selector.ts @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the + * repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { readdirSync, existsSync } from 'fs'; +import { join } from 'path'; +import { log, setOutput, getExtensionInfo } from '../core/utils.js'; + +/** + * Get all available NPM packages + */ +export function getAvailableNpmPackages(): string[] { + log.info('Getting all available NPM packages...'); + + // Get all packages from the packages directory (configurable via PACKAGES_ROOT) + const packagesRoot = process.env.PACKAGES_ROOT || 'packages'; + const packagesDir = join(process.cwd(), packagesRoot); + const packages: string[] = []; + + if (!existsSync(packagesDir)) { + log.warning('packages directory not found'); + return []; + } + + const packageDirs = readdirSync(packagesDir, { withFileTypes: true }) + .filter((dirent) => dirent.isDirectory()) + .map((dirent) => dirent.name); + + for (const packageName of packageDirs) { + const packagePath = join(packagesDir, packageName); + const packageJsonPath = join(packagePath, 'package.json'); + + if (existsSync(packageJsonPath)) { + try { + const info = getExtensionInfo(packagePath); + + // Only include packages that don't have a publisher (NPM packages) + if (!info.publisher) { + packages.push(packageName); + log.debug(`Found NPM package: ${packageName}`); + } else { + log.debug( + `Skipping VS Code extension: ${packageName} (publisher: ${info.publisher})`, + ); + } + } catch (error) { + log.warning(`Failed to read package.json for ${packageName}: ${error}`); + } + } + } + + log.info(`Found ${packages.length} NPM packages: ${packages.join(', ')}`); + return packages; +} + +/** + * Parse user-selected packages from environment variable + */ +function parseUserSelectedPackages(selectedPackagesInput?: string): string[] { + if (!selectedPackagesInput || selectedPackagesInput.trim() === '') { + log.info('No user selection provided - will use all available packages'); + return []; + } + + const selected = selectedPackagesInput + .split(',') + .map((s) => s.trim()) + .filter(Boolean); + log.info(`User selected packages: ${selected.join(', ')}`); + return selected; +} + +/** + * Intersect user selection with detected changes + */ +function intersectPackages( + userSelected: string[], + changedPackages: string[], + availablePackages: string[], +): string[] { + // If no user selection, use all changed packages + if (userSelected.length === 0) { + log.info('No user selection - using all detected changes'); + return changedPackages; + } + + // Handle special values + const normalizedSelection = userSelected.map((s) => s.toLowerCase()); + + if (normalizedSelection.includes('none')) { + log.info('User selected "none" - returning empty selection'); + return []; + } + + if (normalizedSelection.includes('all')) { + log.info('User selected "all" - using all available packages'); + return availablePackages; + } + + if (normalizedSelection.includes('changed')) { + log.info('User selected "changed" - using all detected changes'); + return changedPackages; + } + + // Validate user selection against available packages + const validUserSelected = userSelected.filter((pkg) => { + if (!availablePackages.includes(pkg)) { + log.warning(`User selected package '${pkg}' is not available - skipping`); + return false; + } + return true; + }); + + if (validUserSelected.length === 0) { + log.warning('No valid packages in user selection'); + return []; + } + + // For specific package selection, intersect with detected changes + const intersection = validUserSelected.filter((pkg) => + changedPackages.includes(pkg), + ); + + log.info(`User selection: ${validUserSelected.join(', ')}`); + log.info(`Detected changes: ${changedPackages.join(', ')}`); + log.info(`Intersection: ${intersection.join(', ')}`); + + return intersection; +} + +/** + * Select NPM packages based on user input and detected changes + */ +export function selectNpmPackages( + userSelectedPackages?: string, + availablePackages?: string, + changedPackages?: string, +): string[] { + log.info('Selecting NPM packages for release...'); + log.debug(`User selected packages: ${userSelectedPackages || 'none'}`); + log.debug(`Available packages: ${availablePackages || 'none'}`); + log.debug(`Changed packages: ${changedPackages || 'none'}`); + + // Parse inputs + const userSelected = parseUserSelectedPackages(userSelectedPackages); + const available = availablePackages + ? availablePackages.split(',').filter(Boolean) + : getAvailableNpmPackages(); + const changed = changedPackages + ? changedPackages.split(',').filter(Boolean) + : []; + + log.info(`Available packages: ${available.join(', ')}`); + log.info(`Changed packages: ${changed.join(', ')}`); + + // Intersect user selection with detected changes + const finalSelectedPackages = intersectPackages( + userSelected, + changed, + available, + ); + + log.info(`Final selected packages: ${finalSelectedPackages.join(', ')}`); + return finalSelectedPackages; +} + +/** + * Set GitHub Actions outputs for package selection + */ +export function setPackageSelectionOutputs(selectedPackages: string[]): void { + setOutput('packages', JSON.stringify(selectedPackages)); + log.success('NPM package selection outputs set'); +} + +/** + * Set GitHub Actions outputs for package discovery + */ +export function setPackageDiscoveryOutputs(npmPackages: string[]): void { + setOutput('npm-packages', JSON.stringify(npmPackages)); + log.success('NPM package discovery outputs set'); +} + +/** + * Main function for CLI usage + */ +export async function main(): Promise { + try { + const userSelectedPackages = process.env.SELECTED_PACKAGE; + const availablePackages = process.env.AVAILABLE_PACKAGES; + const changedPackages = process.env.CHANGED_PACKAGES; + + log.debug(`SELECTED_PACKAGE: "${userSelectedPackages}"`); + log.debug(`AVAILABLE_PACKAGES: "${availablePackages}"`); + log.debug(`CHANGED_PACKAGES: "${changedPackages}"`); + + // If we have selection parameters, handle package selection + if (userSelectedPackages || availablePackages || changedPackages) { + log.info('Handling package selection...'); + const selectedPackages = selectNpmPackages( + userSelectedPackages, + availablePackages, + changedPackages, + ); + setPackageSelectionOutputs(selectedPackages); + } else { + // Otherwise, just discover available packages + log.info('Discovering available packages...'); + const npmPackages = getAvailableNpmPackages(); + setPackageDiscoveryOutputs(npmPackages); + } + } catch (error) { + log.error(`Failed to handle NPM packages: ${error}`); + process.exit(1); + } +} + +// Export main function for use in index.ts +export { main as npmPackageSelectorMain }; + +// Run if called directly +if (import.meta.url === `file://${process.argv[1]}`) { + main(); +} diff --git a/packages/vscode-extension-ci/src/npm/npm-release-plan.ts b/packages/vscode-extension-ci/src/npm/npm-release-plan.ts new file mode 100644 index 00000000..9be14b70 --- /dev/null +++ b/packages/vscode-extension-ci/src/npm/npm-release-plan.ts @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the + * repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { readFileSync, existsSync } from 'fs'; +import { join } from 'path'; +import { NpmReleasePlan, VersionBumpType } from './npm-types.js'; +import { log, parseVersion, formatVersion } from '../core/utils.js'; + +/** + * Calculate new version based on current version and bump type + */ +function calculateNewVersion( + currentVersion: string, + versionBump: VersionBumpType, +): string { + try { + const { major, minor, patch } = parseVersion(currentVersion); + + switch (versionBump) { + case 'major': + return formatVersion(major + 1, 0, 0); + case 'minor': + return formatVersion(major, minor + 1, 0); + case 'patch': + return formatVersion(major, minor, patch + 1); + default: + log.warning( + `Unknown version bump type: ${versionBump}, defaulting to patch`, + ); + return formatVersion(major, minor, patch + 1); + } + } catch (error) { + log.error(`Failed to calculate new version: ${error}`); + return currentVersion; + } +} + +/** + * Get package information + */ +function getPackageInfo(packageName: string): { + name: string; + version: string; +} | null { + const packagePath = join(process.cwd(), 'packages', packageName); + const packageJsonPath = join(packagePath, 'package.json'); + + if (!existsSync(packageJsonPath)) { + log.warning(`package.json not found for ${packageName}`); + return null; + } + + try { + const content = readFileSync(packageJsonPath, 'utf-8'); + const pkg = JSON.parse(content); + + return { + name: pkg.name || packageName, + version: pkg.version || '0.0.0', + }; + } catch (error) { + log.warning(`Failed to parse package.json for ${packageName}: ${error}`); + return null; + } +} + +/** + * Generate release plan for a package + */ +export function generateReleasePlan( + packageName: string, + versionBump: VersionBumpType, + dryRun: boolean = false, +): NpmReleasePlan | null { + log.info(`Generating release plan for ${packageName}...`); + + const packageInfo = getPackageInfo(packageName); + if (!packageInfo) { + log.error(`Failed to get package info for ${packageName}`); + return null; + } + + const newVersion = calculateNewVersion(packageInfo.version, versionBump); + + log.info(`Package: ${packageInfo.name}`); + log.info(`Current version: ${packageInfo.version}`); + log.info(`New version: ${newVersion}`); + log.info(`Version bump: ${versionBump}`); + log.info(`Dry run: ${dryRun}`); + + return { + package: packageInfo.name, + currentVersion: packageInfo.version, + newVersion, + versionBump, + dryRun, + }; +} + +/** + * Display release plan + */ +export function displayReleasePlan(plan: NpmReleasePlan): void { + console.log('=== NPM RELEASE PLAN ==='); + console.log(`Package: ${plan.package}`); + console.log(`Current version: ${plan.currentVersion}`); + console.log(`New version: ${plan.newVersion}`); + console.log(`Version bump type: ${plan.versionBump}`); + console.log(`Dry run mode: ${plan.dryRun ? 'ENABLED' : 'DISABLED'}`); + console.log(''); + console.log(`Would bump to: ${plan.newVersion}`); + console.log('Would publish to: npmjs.org'); + console.log(''); +} + +/** + * Main function for CLI usage + */ +export async function main(): Promise { + try { + const packageName = process.env.MATRIX_PACKAGE; + const versionBump = + (process.env.VERSION_BUMP as VersionBumpType) || 'patch'; + const dryRun = process.env.DRY_RUN === 'true'; + + if (!packageName) { + log.error('MATRIX_PACKAGE environment variable is required'); + process.exit(1); + } + + const plan = generateReleasePlan(packageName, versionBump, dryRun); + if (plan) { + displayReleasePlan(plan); + } else { + log.error('Failed to generate release plan'); + process.exit(1); + } + } catch (error) { + log.error(`Failed to generate release plan: ${error}`); + process.exit(1); + } +} + +// Run if called directly +if (import.meta.url === `file://${process.argv[1]}`) { + main(); +} diff --git a/packages/vscode-extension-ci/src/npm/npm-types.ts b/packages/vscode-extension-ci/src/npm/npm-types.ts new file mode 100644 index 00000000..1c0e7faf --- /dev/null +++ b/packages/vscode-extension-ci/src/npm/npm-types.ts @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the + * repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +export interface NpmPackageInfo { + name: string; + path: string; + currentVersion: string; + description?: string; + isExtension: boolean; +} + +export interface NpmChangeDetectionResult { + changedPackages: string[]; + selectedPackages: string[]; + versionBump: VersionBumpType; +} + +export type VersionBumpType = 'patch' | 'minor' | 'major'; + +export interface NpmPackageDetails { + packageNames: string[]; + packageVersions: string[]; + packageDescriptions: string[]; + versionBump: VersionBumpType; +} + +export interface NpmReleasePlan { + package: string; + currentVersion: string; + newVersion: string; + versionBump: VersionBumpType; + dryRun: boolean; +} + +export interface NpmEnvironment { + githubEventName: string; + githubRef: string; + githubRefName: string; + githubActor: string; + githubRepository: string; + githubRunId: string; + githubWorkflow: string; + inputs: { + branch?: string; + packages?: string; + availablePackages?: string; + baseBranch?: string; + dryRun?: string; + }; +} diff --git a/packages/vscode-extension-ci/tsconfig.json b/packages/vscode-extension-ci/tsconfig.json new file mode 100644 index 00000000..011eb0d4 --- /dev/null +++ b/packages/vscode-extension-ci/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "lib": ["ES2022"], + "moduleResolution": "bundler", + "rootDir": "./src", + "outDir": "./dist", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "**/*.test.ts"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..66e801da --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "Node16", + "lib": ["ES2022"], + "moduleResolution": "Node16", + "outDir": "./dist", + "rootDir": ".", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "composite": true, + "incremental": true, + "resolveJsonModule": true + }, + "exclude": [ + "node_modules", + "dist", + "**/node_modules", + "**/dist" + ] +}