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 0000000..e13ed32 --- /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 0000000..6a0b3a6 --- /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 0000000..a4f08d3 --- /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 0000000..b7d3b08 --- /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 0000000..d92cd1c --- /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/publish-vsix/action.yml b/.github/actions/vscode/publish-vsix/action.yml new file mode 100644 index 0000000..9ac4c80 --- /dev/null +++ b/.github/actions/vscode/publish-vsix/action.yml @@ -0,0 +1,147 @@ +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: 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 0000000..9ca180b --- /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 0000000..319fb59 --- /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-ci-template.yml b/.github/workflows/vscode-ci-template.yml new file mode 100644 index 0000000..37324b4 --- /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: salesforcecli/github-workflows/.github/actions/vscode/npm-install-with-retries@feat/add-vscode-extension-ci + + - 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: salesforcecli/github-workflows/.github/actions/vscode/npm-install-with-retries@feat/add-vscode-extension-ci + + - name: Run quality tests + run: npm run test:quality + + package: + name: Package + needs: test + if: ${{ needs.test.result == 'success' }} + uses: salesforcecli/github-workflows/.github/workflows/vscode-package.yml@feat/add-vscode-extension-ci + 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-package.yml b/.github/workflows/vscode-package.yml new file mode 100644 index 0000000..3f1dea9 --- /dev/null +++ b/.github/workflows/vscode-package.yml @@ -0,0 +1,222 @@ +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: salesforcecli/github-workflows/.github/actions/vscode/npm-install-with-retries@feat/add-vscode-extension-ci + + # Universal + web-target VSIXs are defined in packages/apex-lsp-vscode-extension (Wireit: package + package-web). + - 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..." + + # Universal + web-target VSIX under packages/ + 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: salesforcecli/github-workflows/.github/actions/vscode/calculate-artifact-name@feat/add-vscode-extension-ci + 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 0000000..2f4edab --- /dev/null +++ b/.github/workflows/vscode-promote-prerelease.yml @@ -0,0 +1,199 @@ +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 + 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' + +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: salesforcecli/github-workflows/.github/actions/vscode/npm-install-with-retries@feat/add-vscode-extension-ci + + - name: Find eligible nightly + id: find + env: + MIN_TAG_AGE_DAYS: ${{ inputs.min-tag-age-days || '7' }} + 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: salesforcecli/github-workflows/.github/actions/vscode/check-ci-status@feat/add-vscode-extension-ci + 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: salesforcecli/github-workflows/.github/actions/vscode/npm-install-with-retries@feat/add-vscode-extension-ci + + - 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 'apex-language-server-extension-*.vsix' ! -name '*-web-*' | 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: salesforcecli/github-workflows/.github/actions/vscode/publish-vsix@feat/add-vscode-extension-ci + 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="marketplace-prerelease-apex-lsp-vscode-extension-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 0000000..a19c7c1 --- /dev/null +++ b/.github/workflows/vscode-publish-extensions.yml @@ -0,0 +1,749 @@ +name: Publish VS Code Extensions + +# Reusable workflow for building, versioning, and publishing VS Code extensions +# +# Usage from consuming repository: +# jobs: +# publish: +# uses: salesforcecli/github-workflows/.github/workflows/vscode/publish-extensions.yml@main +# with: +# 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 +# - Smart version bumping (even/odd minor for stable/pre-release) +# - 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 + +# 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: salesforcecli/github-workflows/.github/actions/vscode/npm-install-with-retries@feat/add-vscode-extension-ci + + - name: Determine changes and version bumps + id: changes + env: + IS_NIGHTLY: 'true' + VERSION_BUMP: 'auto' + PRE_RELEASE: 'true' + IS_PROMOTION: 'false' + SELECTED_EXTENSIONS: ${{ inputs.extensions }} + 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: salesforcecli/github-workflows/.github/actions/vscode/npm-install-with-retries@feat/add-vscode-extension-ci + + - name: Display Extension Release Plan + env: + BRANCH: ${{ inputs.branch || github.ref_name }} + BUILD_TYPE: ${{ github.event_name }} + IS_NIGHTLY: 'true' + VERSION_BUMP: ${{ needs.determine-changes.outputs.version-bumps }} + REGISTRIES: ${{ inputs.registries }} + PRE_RELEASE: 'true' + SELECTED_EXTENSIONS: ${{ needs.determine-changes.outputs.selected-extensions }} + 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: salesforcecli/github-workflows/.github/actions/vscode/npm-install-with-retries@feat/add-vscode-extension-ci + + - 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: 'true' + IS_PROMOTION: 'false' + 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: salesforcecli/github-workflows/.github/actions/vscode/calculate-artifact-name@feat/add-vscode-extension-ci + 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: salesforcecli/github-workflows/.github/workflows/vscode-package.yml@feat/add-vscode-extension-ci + 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: salesforcecli/github-workflows/.github/actions/vscode/npm-install-with-retries@feat/add-vscode-extension-ci + + - name: Determine publish matrix + id: matrix + env: + REGISTRIES: ${{ inputs.registries }} + SELECTED_EXTENSIONS: ${{ needs.determine-changes.outputs.selected-extensions }} + IS_NIGHTLY: 'true' + 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 }}" + # Apex: universal VSIX only (exclude legacy *-web-* platform VSIX if present) + if [[ "$VSIX_PATTERN" == *apex-language-server-extension* ]]; then + VSIX_FILE=$(find "$ARTIFACTS_DIR" -type f -name 'apex-language-server-extension-*.vsix' ! -name '*-web-*' | head -1) + else + VSIX_FILE=$(find "$ARTIFACTS_DIR" -name "$VSIX_PATTERN" | head -1) + fi + + 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: salesforcecli/github-workflows/.github/actions/vscode/publish-vsix@feat/add-vscode-extension-ci + 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: salesforcecli/github-workflows/.github/actions/vscode/npm-install-with-retries@feat/add-vscode-extension-ci + + - 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: 'true' + 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 + 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="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": "🎉 Apex Language Support Extensions Released Successfully!", + "blocks": [ + { + "type": "header", + "text": { + "type": "plain_text", + "text": "🎉 Apex Language Support Extensions 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="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 b512c09..0cec778 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 0000000..44cf291 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,669 @@ +{ + "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/@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/@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/@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/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/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/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/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/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/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/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/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/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/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/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "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/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/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/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/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/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-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/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/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/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/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/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/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/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/node": "^22.15.1", + "@types/semver": "^7.5.8", + "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 0000000..eff0695 --- /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 0000000..6358c8a --- /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/package.json b/packages/vscode-extension-ci/package.json new file mode 100644 index 0000000..628d703 --- /dev/null +++ b/packages/vscode-extension-ci/package.json @@ -0,0 +1,58 @@ +{ + "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": "echo \"Tests not yet implemented\"", + "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/forcedotcom/vscode-extension-ci-toolkit.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": { + "typescript": "^5.9.3", + "@types/node": "^22.15.1", + "@types/semver": "^7.5.8" + }, + "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 0000000..238bd76 --- /dev/null +++ b/packages/vscode-extension-ci/src/cli.ts @@ -0,0 +1,302 @@ +#!/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({ + versionBump: process.env.VERSION_BUMP || 'auto', + selectedExtensions: process.env.SELECTED_EXTENSIONS || '', + preRelease: process.env.PRE_RELEASE || 'false', + isNightly: process.env.IS_NIGHTLY || 'false', + isPromotion: process.env.IS_PROMOTION || 'false', + promotionCommitSha: process.env.PROMOTION_COMMIT_SHA, + }); + } 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 0000000..02e1c3f --- /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 0000000..03cd4d6 --- /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 0000000..7eaa940 --- /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/ext-build-type.ts b/packages/vscode-extension-ci/src/extension/ext-build-type.ts new file mode 100644 index 0000000..2d2f9ed --- /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 0000000..b61ffc1 --- /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 0000000..7f4380e --- /dev/null +++ b/packages/vscode-extension-ci/src/extension/ext-github-releases.ts @@ -0,0 +1,347 @@ +/* + * 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 { execSync } from 'child_process'; +import { readFileSync, writeFileSync, unlinkSync } from 'fs'; +import { basename, 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 findVsixFiles(extension: string, artifactsPath: string): string[] { + try { + // Map extension names to their actual VSIX file patterns + let vsixPattern: string; + switch (extension) { + case 'apex-lsp-vscode-extension': + vsixPattern = '*apex-language-server-extension-*.vsix'; + break; + default: + vsixPattern = `*${extension}*.vsix`; + } + + // 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 + } + } + + // Universal VSIX only: exclude legacy vsce --target web builds (*-web-* in filename) + return foundFiles.filter((f) => !basename(f).includes('-web-')); + } catch (error) { + console.warn(`Warning: Could not find VSIX files for ${extension}:`, error); + return []; + } +} + +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 + const lastTag = execSync( + 'git tag --sort=-version:refname | grep "^v" | head -1', + { encoding: 'utf8' }, + ).trim(); + + if (lastTag) { + // Get commits since the last release + const recentCommits = execSync( + `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 = execSync( + `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} (odd minor for pre-release)\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; + const vsixArgs = vsixFiles.map((file) => `"${file}"`).join(' '); + + // Check if release already exists (idempotency) + let releaseExists = false; + let hasAssets = false; + try { + const viewOutput = execSync( + `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`, + ); + execSync( + `gh release upload "${releaseTag}" ${vsixArgs} --repo "${repo}"`, + { stdio: 'inherit' }, + ); + console.log(`✅ Assets uploaded to existing release for ${extension}`); + } else { + // Verify the tag exists locally before attempting release creation + try { + execSync(`git rev-parse "${releaseTag}"`, { encoding: 'utf8', stdio: 'pipe' }); + } catch { + // Tag doesn't exist locally — gh release create will create one from HEAD + } + + // 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 command = + `gh release create "${releaseTag}" --title "${releaseTitle}" ` + + `--notes-file "${notesFile}" --prerelease="${preRelease}" ` + + `--repo "${repo}" ${vsixArgs}`; + + try { + execSync(command, { 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 0000000..0f97a00 --- /dev/null +++ b/packages/vscode-extension-ci/src/extension/ext-nightly-finder.ts @@ -0,0 +1,204 @@ +/* + * 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); + + 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 tagPrefix = process.env.TAG_PREFIX || 'marketplace'; + const preReleaseTrackingPrefix = `${tagPrefix}-prerelease-`; + if (hasTrackingTag(allTagNames, `${preReleaseTrackingPrefix}`)) { + // Check specifically for this version + const versionSpecificPrefix = `${tagPrefix}-prerelease-apex-lsp-vscode-extension-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-apex-lsp-vscode-extension-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 0000000..bc7e421 --- /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 0000000..384636b --- /dev/null +++ b/packages/vscode-extension-ci/src/extension/ext-publish-matrix.ts @@ -0,0 +1,152 @@ +#!/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 { + switch (extension) { + case 'apex-lsp-vscode-extension': + // Universal VSIX (main + browser); nightly-extensions publish uses explicit find excluding *-web-* + return '*apex-language-server-extension*-[0-9]*.vsix'; + default: + return `*${extension}*.vsix`; + } +} + +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 0000000..7b432ec --- /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 0000000..57e96d3 --- /dev/null +++ b/packages/vscode-extension-ci/src/extension/ext-version-bumper.ts @@ -0,0 +1,288 @@ +/* + * 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 { execSync } from 'child_process'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +interface PackageJson { + name: string; + version: string; + publisher?: string; + displayName?: string; +} + +interface VersionBumpOptions { + versionBump: string; + selectedExtensions: string; + preRelease: string; + isNightly: string; + isPromotion: string; + promotionCommitSha?: string; +} + +// Export for use in other modules +export type { VersionBumpOptions }; + +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, + isNightly: boolean, + isPromotion: boolean, + preRelease: boolean, +): string { + const { major, minor, patch } = parseVersion(currentVersion); + + if (isNightly) { + // Nightly build strategy: respect conventional commit bump type, enforce odd minor + if (versionBump === 'major') { + // Breaking change: new major, start at first odd minor + return `${major + 1}.1.0`; + } else if (versionBump === 'minor') { + // New feature: skip to next odd minor (even minors reserved for stable) + const nextMinor = minor % 2 === 0 ? minor + 1 : minor + 2; + return `${major}.${nextMinor}.0`; + } else { + // Patch / auto / default: ensure odd minor then increment patch + if (minor % 2 === 0) { + // Even minor — enter nightly track at next odd minor + return `${major}.${minor + 1}.0`; + } + return `${major}.${minor}.${patch + 1}`; + } + } else if (isPromotion) { + // Promotion strategy: bump from odd minor (nightly) to even minor (stable) + if (minor % 2 === 1) { + // Current is odd (nightly), bump to next even (stable) + return `${major}.${minor + 1}.0`; + } else { + // Current is already even, this shouldn't happen for promotions + console.warn( + 'Warning: Current version has even minor, expected odd for promotion', + ); + return `${major}.${minor + 2}.0`; + } + } else { + // Regular build strategy: use smart version bumping + switch (versionBump) { + case 'patch': + return `${major}.${minor}.${patch + 1}`; + case 'minor': + if (preRelease) { + // Pre-release: ensure odd minor version (no auto-update) + if (minor % 2 === 0) { + return `${major}.${minor + 1}.0`; + } else { + return `${major}.${minor + 2}.0`; + } + } else { + // Stable release: ensure even minor version (auto-update enabled) + if (minor % 2 === 1) { + return `${major}.${minor + 1}.0`; + } else { + return `${major}.${minor + 2}.0`; + } + } + case 'major': + if (preRelease) { + return `${major + 1}.1.0`; // Pre-release: start with odd minor + } else { + return `${major + 1}.0.0`; // Stable release: start with even minor + } + 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 createGitTag( + packageName: string, + version: string, + isPreRelease: boolean, + promotionCommitSha?: string, + isNightly?: boolean, +): void { + // For nightly builds, create tag in format: v{version}-nightly.{date} + // This matches what GitHub release creation expects + 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 { + // For non-nightly builds, use package name format + tagName = isPreRelease + ? `${packageName}-v${version}-pre-release` + : `${packageName}-v${version}`; + } + + try { + // Check if tag already exists locally or remotely (idempotency) + let tagExists = false; + try { + // Check local tags first + execSync(`git rev-parse "${tagName}"`, { encoding: 'utf8', stdio: 'pipe' }); + tagExists = true; + } catch { + // Tag doesn't exist locally, check remote + try { + execSync(`git ls-remote --tags origin "${tagName}"`, { encoding: 'utf8', stdio: 'pipe' }); + tagExists = true; + } catch { + // Tag doesn't exist locally or remotely, proceed to create + tagExists = false; + } + } + + if (tagExists) { + console.log(`⏭️ Tag ${tagName} already exists — skipping (idempotent rerun)`); + return; + } + + if (promotionCommitSha) { + // For promotions, create tag on specific commit + console.log( + `Creating tag ${tagName} on promotion commit ${promotionCommitSha}...`, + ); + execSync(`git tag "${tagName}" "${promotionCommitSha}"`, { + stdio: 'inherit', + }); + } else { + // For regular builds, create tag on current commit + console.log(`Creating tag ${tagName} on current commit...`); + execSync(`git tag "${tagName}"`, { + stdio: 'inherit', + }); + } + console.log(`✅ Tag created: ${tagName}`); + } catch (error) { + console.error(`Failed to create tag ${tagName}:`, error); + throw error; + } +} + +function bumpVersions(options: VersionBumpOptions): void { + const { + versionBump, + selectedExtensions, + preRelease, + isNightly, + isPromotion, + promotionCommitSha, + } = options; + + console.log(`Version bump type: ${versionBump}`); + console.log(`Selected extensions: ${selectedExtensions}`); + console.log(`Pre-release mode: ${preRelease}`); + console.log(`Is nightly build: ${isNightly}`); + console.log(`Is promotion: ${isPromotion}`); + console.log(`Promotion commit SHA: ${promotionCommitSha || 'N/A'}`); + + 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 ${ext}...`); + console.log(`Current version: ${packageDetails.version}`); + + const newVersion = calculateNewVersion( + packageDetails.version, + versionBump, + isNightly === 'true', + isPromotion === 'true', + preRelease === 'true', + ); + + console.log( + `🔄 Bumping ${ext} from ${packageDetails.version} to ${newVersion}`, + ); + + // Update package.json version + const originalDir = process.cwd(); + try { + process.chdir(join(originalDir, 'packages', ext)); + execSync(`npm version "${newVersion}" --no-git-tag-version`, { + stdio: 'inherit', + }); + process.chdir(originalDir); + + // Create git tag for this extension + const isNightlyBuild = isNightly === 'true'; + let expectedTagName: string; + if (isNightlyBuild) { + const nightlyDate = new Date() + .toISOString() + .split('T')[0] + .replace(/-/g, ''); + const branch = process.env.BRANCH || 'main'; + const branchSuffix = + branch === 'main' ? '' : `.${branch.replace(/\//g, '-')}`; + expectedTagName = `v${newVersion}-nightly${branchSuffix}.${nightlyDate}`; + } else { + expectedTagName = preRelease === 'true' + ? `${packageDetails.name}-v${newVersion}-pre-release` + : `${packageDetails.name}-v${newVersion}`; + } + createGitTag( + packageDetails.name, + newVersion, + preRelease === 'true', + promotionCommitSha, + isNightlyBuild, + ); + } catch (error) { + console.error(`Failed to bump version for ${ext}:`, error); + process.chdir(originalDir); + throw error; + } + } + + console.log('✅ Version bumps and tags applied'); +} + +// Export for use in other modules +export { bumpVersions }; diff --git a/packages/vscode-extension-ci/src/index.ts b/packages/vscode-extension-ci/src/index.ts new file mode 100644 index 0000000..e51351e --- /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 0000000..b2b904c --- /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 0000000..09e556b --- /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 0000000..a8a5730 --- /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 0000000..9be14b7 --- /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 0000000..1c0e7fa --- /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 0000000..011eb0d --- /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/packages/vscode-extension-ci/tsconfig.tsbuildinfo b/packages/vscode-extension-ci/tsconfig.tsbuildinfo new file mode 100644 index 0000000..86018d4 --- /dev/null +++ b/packages/vscode-extension-ci/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"fileNames":["../../node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/typescript/lib/lib.es2021.d.ts","../../node_modules/typescript/lib/lib.es2022.d.ts","../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/typescript/lib/lib.es2016.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/typescript/lib/lib.es2021.promise.d.ts","../../node_modules/typescript/lib/lib.es2021.string.d.ts","../../node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../node_modules/typescript/lib/lib.es2021.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.array.d.ts","../../node_modules/typescript/lib/lib.es2022.error.d.ts","../../node_modules/typescript/lib/lib.es2022.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.object.d.ts","../../node_modules/typescript/lib/lib.es2022.string.d.ts","../../node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../node_modules/commander/typings/index.d.ts","../../node_modules/commander/typings/esm.d.mts","./src/core/types.ts","../../node_modules/zod/v4/core/json-schema.d.cts","../../node_modules/zod/v4/core/standard-schema.d.cts","../../node_modules/zod/v4/core/registries.d.cts","../../node_modules/zod/v4/core/to-json-schema.d.cts","../../node_modules/zod/v4/core/util.d.cts","../../node_modules/zod/v4/core/versions.d.cts","../../node_modules/zod/v4/core/schemas.d.cts","../../node_modules/zod/v4/core/checks.d.cts","../../node_modules/zod/v4/core/errors.d.cts","../../node_modules/zod/v4/core/core.d.cts","../../node_modules/zod/v4/core/parse.d.cts","../../node_modules/zod/v4/core/regexes.d.cts","../../node_modules/zod/v4/locales/ar.d.cts","../../node_modules/zod/v4/locales/az.d.cts","../../node_modules/zod/v4/locales/be.d.cts","../../node_modules/zod/v4/locales/bg.d.cts","../../node_modules/zod/v4/locales/ca.d.cts","../../node_modules/zod/v4/locales/cs.d.cts","../../node_modules/zod/v4/locales/da.d.cts","../../node_modules/zod/v4/locales/de.d.cts","../../node_modules/zod/v4/locales/el.d.cts","../../node_modules/zod/v4/locales/en.d.cts","../../node_modules/zod/v4/locales/eo.d.cts","../../node_modules/zod/v4/locales/es.d.cts","../../node_modules/zod/v4/locales/fa.d.cts","../../node_modules/zod/v4/locales/fi.d.cts","../../node_modules/zod/v4/locales/fr.d.cts","../../node_modules/zod/v4/locales/fr-ca.d.cts","../../node_modules/zod/v4/locales/he.d.cts","../../node_modules/zod/v4/locales/hr.d.cts","../../node_modules/zod/v4/locales/hu.d.cts","../../node_modules/zod/v4/locales/hy.d.cts","../../node_modules/zod/v4/locales/id.d.cts","../../node_modules/zod/v4/locales/is.d.cts","../../node_modules/zod/v4/locales/it.d.cts","../../node_modules/zod/v4/locales/ja.d.cts","../../node_modules/zod/v4/locales/ka.d.cts","../../node_modules/zod/v4/locales/kh.d.cts","../../node_modules/zod/v4/locales/km.d.cts","../../node_modules/zod/v4/locales/ko.d.cts","../../node_modules/zod/v4/locales/lt.d.cts","../../node_modules/zod/v4/locales/mk.d.cts","../../node_modules/zod/v4/locales/ms.d.cts","../../node_modules/zod/v4/locales/nl.d.cts","../../node_modules/zod/v4/locales/no.d.cts","../../node_modules/zod/v4/locales/ota.d.cts","../../node_modules/zod/v4/locales/ps.d.cts","../../node_modules/zod/v4/locales/pl.d.cts","../../node_modules/zod/v4/locales/pt.d.cts","../../node_modules/zod/v4/locales/ro.d.cts","../../node_modules/zod/v4/locales/ru.d.cts","../../node_modules/zod/v4/locales/sl.d.cts","../../node_modules/zod/v4/locales/sv.d.cts","../../node_modules/zod/v4/locales/ta.d.cts","../../node_modules/zod/v4/locales/th.d.cts","../../node_modules/zod/v4/locales/tr.d.cts","../../node_modules/zod/v4/locales/ua.d.cts","../../node_modules/zod/v4/locales/uk.d.cts","../../node_modules/zod/v4/locales/ur.d.cts","../../node_modules/zod/v4/locales/uz.d.cts","../../node_modules/zod/v4/locales/vi.d.cts","../../node_modules/zod/v4/locales/zh-cn.d.cts","../../node_modules/zod/v4/locales/zh-tw.d.cts","../../node_modules/zod/v4/locales/yo.d.cts","../../node_modules/zod/v4/locales/index.d.cts","../../node_modules/zod/v4/core/doc.d.cts","../../node_modules/zod/v4/core/api.d.cts","../../node_modules/zod/v4/core/json-schema-processors.d.cts","../../node_modules/zod/v4/core/json-schema-generator.d.cts","../../node_modules/zod/v4/core/index.d.cts","../../node_modules/zod/v4/classic/errors.d.cts","../../node_modules/zod/v4/classic/parse.d.cts","../../node_modules/zod/v4/classic/schemas.d.cts","../../node_modules/zod/v4/classic/checks.d.cts","../../node_modules/zod/v4/classic/compat.d.cts","../../node_modules/zod/v4/classic/from-json-schema.d.cts","../../node_modules/zod/v4/classic/iso.d.cts","../../node_modules/zod/v4/classic/coerce.d.cts","../../node_modules/zod/v4/classic/external.d.cts","../../node_modules/zod/index.d.cts","./node_modules/chalk/source/vendor/ansi-styles/index.d.ts","./node_modules/chalk/source/vendor/supports-color/index.d.ts","./node_modules/chalk/source/index.d.ts","../../node_modules/@types/semver/functions/inc.d.ts","../../node_modules/@types/semver/classes/semver.d.ts","../../node_modules/@types/semver/functions/parse.d.ts","../../node_modules/@types/semver/functions/valid.d.ts","../../node_modules/@types/semver/functions/clean.d.ts","../../node_modules/@types/semver/functions/diff.d.ts","../../node_modules/@types/semver/functions/major.d.ts","../../node_modules/@types/semver/functions/minor.d.ts","../../node_modules/@types/semver/functions/patch.d.ts","../../node_modules/@types/semver/functions/prerelease.d.ts","../../node_modules/@types/semver/functions/compare.d.ts","../../node_modules/@types/semver/functions/rcompare.d.ts","../../node_modules/@types/semver/functions/compare-loose.d.ts","../../node_modules/@types/semver/functions/compare-build.d.ts","../../node_modules/@types/semver/functions/sort.d.ts","../../node_modules/@types/semver/functions/rsort.d.ts","../../node_modules/@types/semver/functions/gt.d.ts","../../node_modules/@types/semver/functions/lt.d.ts","../../node_modules/@types/semver/functions/eq.d.ts","../../node_modules/@types/semver/functions/neq.d.ts","../../node_modules/@types/semver/functions/gte.d.ts","../../node_modules/@types/semver/functions/lte.d.ts","../../node_modules/@types/semver/functions/cmp.d.ts","../../node_modules/@types/semver/functions/coerce.d.ts","../../node_modules/@types/semver/classes/comparator.d.ts","../../node_modules/@types/semver/classes/range.d.ts","../../node_modules/@types/semver/functions/satisfies.d.ts","../../node_modules/@types/semver/ranges/max-satisfying.d.ts","../../node_modules/@types/semver/ranges/min-satisfying.d.ts","../../node_modules/@types/semver/ranges/to-comparators.d.ts","../../node_modules/@types/semver/ranges/min-version.d.ts","../../node_modules/@types/semver/ranges/valid.d.ts","../../node_modules/@types/semver/ranges/outside.d.ts","../../node_modules/@types/semver/ranges/gtr.d.ts","../../node_modules/@types/semver/ranges/ltr.d.ts","../../node_modules/@types/semver/ranges/intersects.d.ts","../../node_modules/@types/semver/ranges/simplify.d.ts","../../node_modules/@types/semver/ranges/subset.d.ts","../../node_modules/@types/semver/internals/identifiers.d.ts","../../node_modules/@types/semver/index.d.ts","./src/core/utils.ts","./src/extension/ext-build-type.ts","../../node_modules/simple-git/dist/src/lib/tasks/diff-name-status.d.ts","../../node_modules/simple-git/dist/src/lib/tasks/task.d.ts","../../node_modules/simple-git/dist/src/lib/types/tasks.d.ts","../../node_modules/simple-git/dist/src/lib/errors/git-error.d.ts","../../node_modules/simple-git/dist/src/lib/types/handlers.d.ts","../../node_modules/@simple-git/argv-parser/dist/src/vulnerabilities/vulnerability.types.d.ts","../../node_modules/@simple-git/argv-parser/dist/src/args/parse-argv.types.d.ts","../../node_modules/@simple-git/argv-parser/dist/src/args/parse-argv.d.ts","../../node_modules/@simple-git/argv-parser/dist/src/env/parse-env.d.ts","../../node_modules/@simple-git/argv-parser/dist/src/vulnerabilities/vulnerability-check.d.ts","../../node_modules/@simple-git/argv-parser/dist/index.d.ts","../../node_modules/simple-git/dist/src/lib/types/index.d.ts","../../node_modules/simple-git/dist/src/lib/tasks/log.d.ts","../../node_modules/simple-git/dist/typings/response.d.ts","../../node_modules/simple-git/dist/src/lib/responses/getremotesummary.d.ts","../../node_modules/simple-git/dist/src/lib/tasks/apply-patch.d.ts","../../node_modules/simple-git/dist/src/lib/tasks/check-is-repo.d.ts","../../node_modules/simple-git/dist/src/lib/tasks/clean.d.ts","../../node_modules/simple-git/dist/src/lib/tasks/clone.d.ts","../../node_modules/simple-git/dist/src/lib/tasks/config.d.ts","../../node_modules/simple-git/dist/src/lib/tasks/count-objects.d.ts","../../node_modules/simple-git/dist/src/lib/tasks/grep.d.ts","../../node_modules/simple-git/dist/src/lib/tasks/reset.d.ts","../../node_modules/simple-git/dist/src/lib/tasks/version.d.ts","../../node_modules/simple-git/dist/typings/types.d.ts","../../node_modules/simple-git/dist/src/lib/errors/git-construct-error.d.ts","../../node_modules/simple-git/dist/src/lib/errors/git-plugin-error.d.ts","../../node_modules/simple-git/dist/src/lib/errors/git-response-error.d.ts","../../node_modules/simple-git/dist/src/lib/errors/task-configuration-error.d.ts","../../node_modules/simple-git/dist/typings/errors.d.ts","../../node_modules/simple-git/dist/typings/simple-git.d.ts","../../node_modules/@simple-git/args-pathspec/dist/src/pathspec.d.ts","../../node_modules/@simple-git/args-pathspec/dist/index.d.ts","../../node_modules/simple-git/dist/typings/index.d.ts","./src/extension/ext-nightly-finder.ts","./src/extension/ext-change-detector.ts","./src/extension/ext-package-selector.ts","./src/npm/npm-types.ts","./src/npm/npm-change-detector.ts","./src/npm/npm-package-selector.ts","./src/npm/npm-package-details.ts","./src/npm/npm-release-plan.ts","./src/extension/ext-release-plan.ts","./src/extension/ext-version-bumper.ts","./src/extension/ext-publish-matrix.ts","../../node_modules/minipass/dist/esm/index.d.ts","../../node_modules/@types/node/compatibility/disposable.d.ts","../../node_modules/@types/node/compatibility/indexable.d.ts","../../node_modules/@types/node/compatibility/iterators.d.ts","../../node_modules/@types/node/compatibility/index.d.ts","../../node_modules/@types/node/globals.typedarray.d.ts","../../node_modules/@types/node/buffer.buffer.d.ts","../../node_modules/@types/node/globals.d.ts","../../node_modules/@types/node/web-globals/abortcontroller.d.ts","../../node_modules/@types/node/web-globals/domexception.d.ts","../../node_modules/@types/node/web-globals/events.d.ts","../../../node_modules/buffer/index.d.ts","../../node_modules/undici-types/header.d.ts","../../node_modules/undici-types/readable.d.ts","../../node_modules/undici-types/file.d.ts","../../node_modules/undici-types/fetch.d.ts","../../node_modules/undici-types/formdata.d.ts","../../node_modules/undici-types/connector.d.ts","../../node_modules/undici-types/client.d.ts","../../node_modules/undici-types/errors.d.ts","../../node_modules/undici-types/dispatcher.d.ts","../../node_modules/undici-types/global-dispatcher.d.ts","../../node_modules/undici-types/global-origin.d.ts","../../node_modules/undici-types/pool-stats.d.ts","../../node_modules/undici-types/pool.d.ts","../../node_modules/undici-types/handlers.d.ts","../../node_modules/undici-types/balanced-pool.d.ts","../../node_modules/undici-types/agent.d.ts","../../node_modules/undici-types/mock-interceptor.d.ts","../../node_modules/undici-types/mock-agent.d.ts","../../node_modules/undici-types/mock-client.d.ts","../../node_modules/undici-types/mock-pool.d.ts","../../node_modules/undici-types/mock-errors.d.ts","../../node_modules/undici-types/proxy-agent.d.ts","../../node_modules/undici-types/env-http-proxy-agent.d.ts","../../node_modules/undici-types/retry-handler.d.ts","../../node_modules/undici-types/retry-agent.d.ts","../../node_modules/undici-types/api.d.ts","../../node_modules/undici-types/interceptors.d.ts","../../node_modules/undici-types/util.d.ts","../../node_modules/undici-types/cookies.d.ts","../../node_modules/undici-types/patch.d.ts","../../node_modules/undici-types/websocket.d.ts","../../node_modules/undici-types/eventsource.d.ts","../../node_modules/undici-types/filereader.d.ts","../../node_modules/undici-types/diagnostics-channel.d.ts","../../node_modules/undici-types/content-type.d.ts","../../node_modules/undici-types/cache.d.ts","../../node_modules/undici-types/index.d.ts","../../node_modules/@types/node/web-globals/fetch.d.ts","../../node_modules/@types/node/web-globals/navigator.d.ts","../../node_modules/@types/node/web-globals/storage.d.ts","../../node_modules/@types/node/assert.d.ts","../../node_modules/@types/node/assert/strict.d.ts","../../node_modules/@types/node/async_hooks.d.ts","../../node_modules/@types/node/buffer.d.ts","../../node_modules/@types/node/child_process.d.ts","../../node_modules/@types/node/cluster.d.ts","../../node_modules/@types/node/console.d.ts","../../node_modules/@types/node/constants.d.ts","../../node_modules/@types/node/crypto.d.ts","../../node_modules/@types/node/dgram.d.ts","../../node_modules/@types/node/diagnostics_channel.d.ts","../../node_modules/@types/node/dns.d.ts","../../node_modules/@types/node/dns/promises.d.ts","../../node_modules/@types/node/domain.d.ts","../../node_modules/@types/node/events.d.ts","../../node_modules/@types/node/fs.d.ts","../../node_modules/@types/node/fs/promises.d.ts","../../node_modules/@types/node/http.d.ts","../../node_modules/@types/node/http2.d.ts","../../node_modules/@types/node/https.d.ts","../../node_modules/@types/node/inspector.d.ts","../../node_modules/@types/node/inspector.generated.d.ts","../../node_modules/@types/node/module.d.ts","../../node_modules/@types/node/net.d.ts","../../node_modules/@types/node/os.d.ts","../../node_modules/@types/node/path.d.ts","../../node_modules/@types/node/perf_hooks.d.ts","../../node_modules/@types/node/process.d.ts","../../node_modules/@types/node/punycode.d.ts","../../node_modules/@types/node/querystring.d.ts","../../node_modules/@types/node/readline.d.ts","../../node_modules/@types/node/readline/promises.d.ts","../../node_modules/@types/node/repl.d.ts","../../node_modules/@types/node/sea.d.ts","../../node_modules/@types/node/sqlite.d.ts","../../node_modules/@types/node/stream.d.ts","../../node_modules/@types/node/stream/promises.d.ts","../../node_modules/@types/node/stream/consumers.d.ts","../../node_modules/@types/node/stream/web.d.ts","../../node_modules/@types/node/string_decoder.d.ts","../../node_modules/@types/node/test.d.ts","../../node_modules/@types/node/timers.d.ts","../../node_modules/@types/node/timers/promises.d.ts","../../node_modules/@types/node/tls.d.ts","../../node_modules/@types/node/trace_events.d.ts","../../node_modules/@types/node/tty.d.ts","../../node_modules/@types/node/url.d.ts","../../node_modules/@types/node/util.d.ts","../../node_modules/@types/node/v8.d.ts","../../node_modules/@types/node/vm.d.ts","../../node_modules/@types/node/wasi.d.ts","../../node_modules/@types/node/worker_threads.d.ts","../../node_modules/@types/node/zlib.d.ts","../../node_modules/@types/node/index.d.ts","../../node_modules/lru-cache/dist/esm/index.d.ts","../../node_modules/path-scurry/dist/esm/index.d.ts","../../node_modules/minimatch/dist/esm/ast.d.ts","../../node_modules/minimatch/dist/esm/escape.d.ts","../../node_modules/minimatch/dist/esm/unescape.d.ts","../../node_modules/minimatch/dist/esm/index.d.ts","../../node_modules/glob/dist/esm/pattern.d.ts","../../node_modules/glob/dist/esm/processor.d.ts","../../node_modules/glob/dist/esm/walker.d.ts","../../node_modules/glob/dist/esm/ignore.d.ts","../../node_modules/glob/dist/esm/glob.d.ts","../../node_modules/glob/dist/esm/has-magic.d.ts","../../node_modules/glob/dist/esm/index.d.ts","./src/extension/ext-github-releases.ts","./src/core/audit-logger.ts","./src/cli.ts","./src/index.ts"],"fileIdsList":[[217,237,286,303,304],[237,286,303,304],[191,192,193,194,195,237,286,303,304],[192,237,286,303,304],[191,237,286,303,304],[191,192,237,286,303,304],[237,283,284,286,303,304],[237,285,286,303,304],[286,303,304],[237,286,291,303,304,321],[237,286,287,292,297,303,304,306,318,329],[237,286,287,288,297,303,304,306],[232,233,234,237,286,303,304],[237,286,289,303,304,330],[237,286,290,291,298,303,304,307],[237,286,291,303,304,318,326],[237,286,292,294,297,303,304,306],[237,285,286,293,303,304],[237,286,294,295,303,304],[237,286,296,297,303,304],[237,285,286,297,303,304],[237,286,297,298,299,303,304,318,329],[237,286,297,298,299,303,304,313,318,321],[237,279,286,294,297,300,303,304,306,318,329],[237,286,297,298,300,301,303,304,306,318,326,329],[237,286,300,302,303,304,318,326,329],[235,236,237,238,239,240,241,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335],[237,286,297,303,304],[237,286,303,304,305,329],[237,286,294,297,303,304,306,318],[237,286,303,304,307],[237,286,303,304,308],[237,285,286,303,304,309],[237,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335],[237,286,303,304,311],[237,286,303,304,312],[237,286,297,303,304,313,314],[237,286,303,304,313,315,330,332],[237,286,298,303,304],[237,286,297,303,304,318,319,321],[237,286,303,304,320,321],[237,286,303,304,318,319],[237,286,303,304,321],[237,286,303,304,322],[237,283,286,303,304,318,323,329],[237,286,297,303,304,324,325],[237,286,303,304,324,325],[237,286,291,303,304,306,318,326],[237,286,303,304,327],[237,286,303,304,306,328],[237,286,300,303,304,312,329],[237,286,291,303,304,330],[237,286,303,304,318,331],[237,286,303,304,305,332],[237,286,303,304,333],[237,279,286,303,304],[237,279,286,297,299,303,304,309,318,321,329,331,332,334],[237,286,303,304,318,335],[145,183,237,286,303,304],[145,168,183,237,286,303,304],[144,183,237,286,303,304],[183,237,286,303,304],[145,237,286,303,304],[145,169,183,237,286,303,304],[144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,237,286,303,304],[169,183,237,286,303,304],[58,237,286,303,304],[231,237,286,303,304,338,342,343,346],[237,286,303,304,347],[237,286,303,304,338,342,345],[231,237,286,303,304,338,342,345,346,347,348],[237,286,303,304,342],[237,286,303,304,338,342,343,345],[231,237,286,303,304,338,343,344,346],[237,286,303,304,339,340,341],[237,286,297,303,304,322],[231,237,286,298,303,304,308,336,337],[189,197,237,286,303,304],[197,237,286,303,304],[189,237,286,303,304],[187,197,219,237,286,303,304],[219,237,286,303,304],[197,219,237,286,303,304],[188,190,196,237,286,287,303,304],[187,197,237,286,303,304],[189,211,212,213,214,237,286,303,304],[199,210,215,216,218,237,286,303,304],[186,198,237,286,303,304],[199,210,215,237,286,303,304],[186,197,198,200,201,202,203,204,205,206,207,208,209,237,286,303,304],[237,251,255,286,303,304,329],[237,251,286,303,304,318,329],[237,246,286,303,304],[237,248,251,286,303,304,326,329],[237,286,303,304,306,326],[237,286,303,304,336],[237,246,286,303,304,336],[237,248,251,286,303,304,306,329],[237,243,244,247,250,286,297,303,304,318,329],[237,251,258,286,303,304],[237,243,249,286,303,304],[237,251,272,273,286,303,304],[237,247,251,286,303,304,321,329,336],[237,272,286,303,304,336],[237,245,246,286,303,304,336],[237,251,286,303,304],[237,245,246,247,248,249,250,251,252,253,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,273,274,275,276,277,278,286,303,304],[237,251,266,286,303,304],[237,251,258,259,286,303,304],[237,249,251,259,260,286,303,304],[237,250,286,303,304],[237,243,246,251,286,303,304],[237,251,255,259,260,286,303,304],[237,255,286,303,304],[237,249,251,254,286,303,304,329],[237,243,248,251,258,286,303,304],[237,286,303,304,318],[237,246,251,272,286,303,304,334,336],[139,237,286,303,304],[130,237,286,303,304],[130,133,237,286,303,304],[65,125,128,130,131,132,133,134,135,136,137,138,237,286,303,304],[61,63,133,237,286,303,304],[130,131,237,286,303,304],[62,130,132,237,286,303,304],[63,65,67,68,69,70,237,286,303,304],[65,67,69,70,237,286,303,304],[65,67,69,237,286,303,304],[62,65,67,68,70,237,286,303,304],[61,63,64,65,66,67,68,69,70,71,72,125,126,127,128,129,237,286,303,304],[61,63,64,67,237,286,303,304],[63,64,67,237,286,303,304],[67,70,237,286,303,304],[61,62,64,65,66,68,69,70,237,286,303,304],[61,62,63,67,130,237,286,303,304],[67,68,69,70,237,286,303,304],[69,237,286,303,304],[73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,237,286,303,304],[141,142,237,286,303,304],[237,286,303,304,328],[59,184,185,220,221,222,224,225,226,227,228,229,230,237,286,303,304,350,351],[237,286,298,303,304,308],[60,140,143,183,237,286,298,303,304,308],[60,184,237,286,303,304],[60,184,219,237,286,298,303,304,308],[237,286,287,298,303,304,308,349],[60,183,184,219,237,286,303,304],[184,237,286,298,303,304,308],[237,286,287,298,303,304,308],[60,184,185,220,221,222,224,225,226,227,228,229,230,237,286,303,304,350,351],[184,219,223,237,286,298,303,304,308],[184,223,237,286,298,303,304,308]],"fileInfos":[{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2","impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005","affectsGlobalScope":true,"impliedFormat":1},{"version":"8cdf8847677ac7d20486e54dd3fcf09eda95812ac8ace44b4418da1bbbab6eb8","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"0237580177f1a858ab125992af8a6dfc29268fe7d4bac5308b9f462f88b0cd31","impliedFormat":1},{"version":"b124c0624b15412ace7d54644ade38d7a69db7e25488a1a4d2a8df6e11696538","impliedFormat":99},{"version":"8e589429c011972dacd86b763ab582cb526dc9cf571b6f0a9e1800534aa108e4","signature":"7f321ebf4afebc4ce5d1e49e159f87fef4119a162de6431fca200511e0e80ffe"},{"version":"c1a2e05eb6d7ca8d7e4a7f4c93ccf0c2857e842a64c98eaee4d85841ee9855e6","impliedFormat":1},{"version":"835fb2909ce458740fb4a49fc61709896c6864f5ce3db7f0a88f06c720d74d02","impliedFormat":1},{"version":"6e5857f38aa297a859cab4ec891408659218a5a2610cd317b6dcbef9979459cc","impliedFormat":1},{"version":"ead8e39c2e11891f286b06ae2aa71f208b1802661fcdb2425cffa4f494a68854","impliedFormat":1},{"version":"40ba6c32eb732a09e4446ade5cb6ad0c147f186f9c9dc6878b90b4418ad9f6ea","impliedFormat":1},{"version":"fdd814741843f85c98281522c58f5a646590ba9019fad2efaa95987655e0611b","impliedFormat":1},{"version":"c78aff4fb58b28b8f642d5095fc7eeb79f00e652a67caa19693af1adabb833c9","impliedFormat":1},{"version":"f80a08ced8818dc99359c0acd5b3f12762e1ce53758007759b0d4e503cbf4a5e","impliedFormat":1},{"version":"37935fa7564bcc6e0bc845b766a24391098d26f7c8245d6e8ab37bc016816e94","impliedFormat":1},{"version":"68add36d9632bc096d7245d24d6b0b8ad5f125183016102a3dad4c9c2438ccb0","impliedFormat":1},{"version":"3a819c2928ee06bbcc84e2797fd3558ae2ebb7e0ed8d87f71732fb2e2acc87b4","impliedFormat":1},{"version":"0f8a263f4c8595c8a07de52e3f3927640c44386c1aa2984de9eae50d75e613b2","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d","impliedFormat":1},{"version":"346fffde7c32da87c2196eb7494422449dc2ca82d3b4e6bf55be1d1a33ffc2b0","impliedFormat":1},{"version":"add0ce7b77ba5b308492fa68f77f24d1ed1d9148534bdf05ac17c30763fc1a79","impliedFormat":1},{"version":"8b5875e4958528042103fdd775e106a7f76bafc29709f0690df9a7d2241d52a7","impliedFormat":1},{"version":"2f67911e4bf4e0717dc2ded248ce2d5e4398d945ee13889a6852c1233ea41508","impliedFormat":1},{"version":"d8430c275b0f59417ea8e173cfb888a4477b430ec35b595bf734f3ec7a7d729f","impliedFormat":1},{"version":"69364df1c776372d7df1fb46a6cb3a6bf7f55e700f533a104e3f9d70a32bec18","impliedFormat":1},{"version":"6042774c61ece4ba77b3bf375f15942eb054675b7957882a00c22c0e4fe5865c","impliedFormat":1},{"version":"5a3bd57ed7a9d9afef74c75f77fce79ba3c786401af9810cdf45907c4e93f30e","impliedFormat":1},{"version":"aef26cf95593c8ace1c62c4724f9afac77bdfa756fb8a00613cd152117cb2f43","impliedFormat":1},{"version":"30db853bb2e60170ba11e39ab48bacecb32d06d4def89eedf17e58ebab762a65","impliedFormat":1},{"version":"e27451b24234dfed45f6cf22112a04955183a99c42a2691fb4936d63cfe42761","impliedFormat":1},{"version":"2316301dd223d31962d917999acf8e543e0119c5d24ec984c9f22cb23247160c","impliedFormat":1},{"version":"58d65a2803c3b6629b0e18c8bf1bc883a686fcf0333230dd0151ab6e85b74307","impliedFormat":1},{"version":"e818471014c77c103330aee11f00a7a00b37b35500b53ea6f337aefacd6174c9","impliedFormat":1},{"version":"268fd6d9f2e807a39a6c5aa654b00f949feb63d3faa7dd0f9bba7dde9172159c","impliedFormat":1},{"version":"29f823cbe0166e10e7176a94afe609a24b9e5af3858628c541ff8ce1727023cd","impliedFormat":1},{"version":"acfed6cc001e7f7f26d2ba42222a180ba669bb966d4dd9cb4ad5596516061b13","impliedFormat":99},{"version":"f61a4dc92450609c353738f0a2daebf8cae71b24716dbd952456d80b1e1a48b6","impliedFormat":99},{"version":"f3f76db6e76bc76d13cc4bfa10e1f74390b8ebe279535f62243e8d8acd919314","impliedFormat":99},{"version":"ce6a3f09b8db73a7e9701aca91a04b4fabaf77436dd35b24482f9ee816016b17","impliedFormat":1},{"version":"20e086e5b64fdd52396de67761cc0e94693494deadb731264aac122adf08de3f","impliedFormat":1},{"version":"6e78f75403b3ec65efb41c70d392aeda94360f11cedc9fb2c039c9ea23b30962","impliedFormat":1},{"version":"c863198dae89420f3c552b5a03da6ed6d0acfa3807a64772b895db624b0de707","impliedFormat":1},{"version":"8b03a5e327d7db67112ebbc93b4f744133eda2c1743dbb0a990c61a8007823ef","impliedFormat":1},{"version":"42fad1f540271e35ca37cecda12c4ce2eef27f0f5cf0f8dd761d723c744d3159","impliedFormat":1},{"version":"ff3743a5de32bee10906aff63d1de726f6a7fd6ee2da4b8229054dfa69de2c34","impliedFormat":1},{"version":"83acd370f7f84f203e71ebba33ba61b7f1291ca027d7f9a662c6307d74e4ac22","impliedFormat":1},{"version":"1445cec898f90bdd18b2949b9590b3c012f5b7e1804e6e329fb0fe053946d5ec","impliedFormat":1},{"version":"0e5318ec2275d8da858b541920d9306650ae6ac8012f0e872fe66eb50321a669","impliedFormat":1},{"version":"cf530297c3fb3a92ec9591dd4fa229d58b5981e45fe6702a0bd2bea53a5e59be","impliedFormat":1},{"version":"c1f6f7d08d42148ddfe164d36d7aba91f467dbcb3caa715966ff95f55048b3a4","impliedFormat":1},{"version":"eefd2bbc8edb14c3bd1246794e5c070a80f9b8f3730bd42efb80df3cc50b9039","impliedFormat":1},{"version":"0c1ee27b8f6a00097c2d6d91a21ee4d096ab52c1e28350f6362542b55380059a","impliedFormat":1},{"version":"7677d5b0db9e020d3017720f853ba18f415219fb3a9597343b1b1012cfd699f7","impliedFormat":1},{"version":"bc1c6bc119c1784b1a2be6d9c47addec0d83ef0d52c8fbe1f14a51b4dfffc675","impliedFormat":1},{"version":"52cf2ce99c2a23de70225e252e9822a22b4e0adb82643ab0b710858810e00bf1","impliedFormat":1},{"version":"770625067bb27a20b9826255a8d47b6b5b0a2d3dfcbd21f89904c731f671ba77","impliedFormat":1},{"version":"d1ed6765f4d7906a05968fb5cd6d1db8afa14dbe512a4884e8ea5c0f5e142c80","impliedFormat":1},{"version":"799c0f1b07c092626cf1efd71d459997635911bb5f7fc1196efe449bba87e965","impliedFormat":1},{"version":"2a184e4462b9914a30b1b5c41cf80c6d3428f17b20d3afb711fff3f0644001fd","impliedFormat":1},{"version":"9eabde32a3aa5d80de34af2c2206cdc3ee094c6504a8d0c2d6d20c7c179503cc","impliedFormat":1},{"version":"397c8051b6cfcb48aa22656f0faca2553c5f56187262135162ee79d2b2f6c966","impliedFormat":1},{"version":"a8ead142e0c87dcd5dc130eba1f8eeed506b08952d905c47621dc2f583b1bff9","impliedFormat":1},{"version":"a02f10ea5f73130efca046429254a4e3c06b5475baecc8f7b99a0014731be8b3","impliedFormat":1},{"version":"c2576a4083232b0e2d9bd06875dd43d371dee2e090325a9eac0133fd5650c1cb","impliedFormat":1},{"version":"4c9a0564bb317349de6a24eb4efea8bb79898fa72ad63a1809165f5bd42970dd","impliedFormat":1},{"version":"f40ac11d8859092d20f953aae14ba967282c3bb056431a37fced1866ec7a2681","impliedFormat":1},{"version":"cc11e9e79d4746cc59e0e17473a59d6f104692fd0eeea1bdb2e206eabed83b03","impliedFormat":1},{"version":"b444a410d34fb5e98aa5ee2b381362044f4884652e8bc8a11c8fe14bbd85518e","impliedFormat":1},{"version":"c35808c1f5e16d2c571aa65067e3cb95afeff843b259ecfa2fc107a9519b5392","impliedFormat":1},{"version":"14d5dc055143e941c8743c6a21fa459f961cbc3deedf1bfe47b11587ca4b3ef5","impliedFormat":1},{"version":"a3ad4e1fc542751005267d50a6298e6765928c0c3a8dce1572f2ba6ca518661c","impliedFormat":1},{"version":"f237e7c97a3a89f4591afd49ecb3bd8d14f51a1c4adc8fcae3430febedff5eb6","impliedFormat":1},{"version":"3ffdfbec93b7aed71082af62b8c3e0cc71261cc68d796665faa1e91604fbae8f","impliedFormat":1},{"version":"662201f943ed45b1ad600d03a90dffe20841e725203ced8b708c91fcd7f9379a","impliedFormat":1},{"version":"c9ef74c64ed051ea5b958621e7fb853fe3b56e8787c1587aefc6ea988b3c7e79","impliedFormat":1},{"version":"2462ccfac5f3375794b861abaa81da380f1bbd9401de59ffa43119a0b644253d","impliedFormat":1},{"version":"34baf65cfee92f110d6653322e2120c2d368ee64b3c7981dff08ed105c4f19b0","impliedFormat":1},{"version":"a56fe175741cc8841835eb72e61fa5a34adcbc249ede0e3494c229f0750f6b85","impliedFormat":1},{"version":"f78416aaf37a6d5cc1a5ba7eac58a23970187d743f222cb28e5bf48960dfd301","signature":"ba0c71c69e2ef173e991d125f221ca1c12b1b1a866b2270dac56b7a225cbb37f"},{"version":"5ec5d33400cc5d63fa04d83a2a8d4e94adab5bc4f7f130c077c6fd76195a49ba","signature":"c239bd960f2f85c6576f1cebf0db812cd7fd34e9d158bce344e42894e958ac54"},{"version":"16b81141d0c59af6f07e5fc24824c54dd6003da0ab0a2d2cedc95f8eb03ea8d3","impliedFormat":1},{"version":"6578758b0b94087beffd0ce554701365cd1e6a7428f14464ac8b88095fca4e50","impliedFormat":1},{"version":"b6c4796630a47f8b0f420519cd241e8e7701247b48ed4b205e8d057cbf7107d7","impliedFormat":1},{"version":"6256cf36c8ae7e82bff606595af8fe08a06f8478140fcf304ee2f10c7716ddc8","impliedFormat":1},{"version":"b2dbe6b053e04ec135c7ce722e0a4e9744281ea40429af96e2662cc926465519","impliedFormat":1},{"version":"bd2c8f750fc6fcb45d988603052e89ca7759ce482c49793786e341e262d4a4d6","impliedFormat":1},{"version":"775531f55189af09e075893722a81807b8a3b908e2a2bbb24d2cea4a76de9d38","impliedFormat":1},{"version":"eea465d51d6e997543be5d873145819d1279deda351015d2e836df6b06c6efe9","impliedFormat":1},{"version":"799b38ea6c500092c26b26a456e513cd347c38bf9d26b15c86cbba81bede1022","impliedFormat":1},{"version":"ddb546cbed105fce585d28603d1dc2a8351f8b81b16f69b67d0d29a7a4793042","impliedFormat":1},{"version":"fe0eaceca01f67de63e96e0082ea9d936ac94dffad131ffad65c4dcf3953d94e","impliedFormat":1},{"version":"b62b9d7193265de7526c4d5d777902fdb3150ec6773ab7dd642864d92b8c12a7","impliedFormat":1},{"version":"5619706bbd7a964d7c82cd4a307457ed0327ecc86772ceb7ea0870566c6578b2","impliedFormat":1},{"version":"b48c4e15766170c5003a6273b1d8f17f854ec565ccaaebd9f700fef159b84078","impliedFormat":1},{"version":"7c774169686976056434799723bd7a48348df9d2204b928a0b77920505585214","impliedFormat":1},{"version":"3e697e2186544103572756d80b61fcce3842ab07abdc5a1b7b8d4b9a4136005a","impliedFormat":1},{"version":"8758b438b12ea50fb8b678d29ab0ef42d77abfb801cec481596ce6002b537a6f","impliedFormat":1},{"version":"688a28e7953ef4465f68da2718dc6438aaa16325133a8cb903bf850c63cb4a7e","impliedFormat":1},{"version":"f2c96e813200ca900d2bbf0d8e3db7c2a70180b80f559b676aab8315c9472943","impliedFormat":1},{"version":"f73cf81342d2a25b65179c262ca7c38df023969129094607d0eb52510a56f10f","impliedFormat":1},{"version":"f433d28f86313073f13b16c0a18ccdd21759390f52c8d7bf9d916645b12d16ed","impliedFormat":1},{"version":"e7d7e67bd66b30f2216e4678b97bb09629a2b31766a79119acaa30e3005ef5fb","impliedFormat":1},{"version":"e05a20aa85c7324c65643542c2d7314774c2adf510f9dcbad5d3afac74ca3dac","impliedFormat":1},{"version":"e137f087bda0256410b28743ef9a1bf57a4cafd43ffa6b62d5c17a8f5a08b3b5","impliedFormat":1},{"version":"dd3e50008131e83844c49f80f7dc8e5a8fd8d1fbe0bc4da26121dda14488e507","impliedFormat":1},{"version":"af504042a6db047c40cc0aeb14550bbc954f194f2b8c5ad8944f2da502f45bf5","impliedFormat":1},{"version":"5b25b6ab5ad6c17f90b592162b2e9978ad8d81edf24cd3957306eb6e5edb89a9","impliedFormat":1},{"version":"24693bd77ac3be0b16e564d0ab498a397feb758ce7f4ed9f13478d566e3aafde","impliedFormat":1},{"version":"208dad548b895c7d02465de6ba79064b7c67bc4d94e5227b09f21d58790e634c","impliedFormat":1},{"version":"048c0ced65fa41fbf4bcc3d5e8e5b6f6c7f27335ceb54d401be654e821adbc08","impliedFormat":1},{"version":"e1126668c194faa56a728eb5bd2dd88dfc19460ced65c38888977a22369e4624","impliedFormat":1},{"version":"1df8dcaa6969c836cfe30ae6c0b91d2d94c8706be13ac55d203a639741908a76","impliedFormat":1},{"version":"e84e587a2392736f944496a5805c787473fd3edc5a5d8046c0063b2334c05fcc","impliedFormat":1},{"version":"fe54919026a7e4762fc03f58baa8a2bf856af8de0eaae0a0d27265fa122565e5","impliedFormat":1},{"version":"14503f3aeebc186d97c9c20d46fe2b47fd5bc9aac30fce21e81b7e784704a039","signature":"145992f793baaffc171c145712d87bf3c46bf4e0625f3a8b049df8fa8c3f172a"},{"version":"0ce113f155c5bc572300d30adf445fdecfe482880ac97c7f8f25f6f5dde62c10","signature":"5e9ac2a1944c33f6ec34f1e049bb1d7eb0325c260873add35db00efc118f31d6"},{"version":"b1f0cc58ae822dc353388eccbffa26f9f542022eebb34d8d7fa64173149c5eb9","signature":"e47dbd8ec06ea719bbd4f848da9f4550aaf8ff8710ee28c0dc20ee25b481452a"},{"version":"96e2f907a0d895a195b5ff75e7660700a922e9f1805139a38fe55c7435577a49","signature":"761e23b35328908906b8b186f8fed586c48861203430f0cb9f89c9320b85f818"},{"version":"0da639e55badb875f4bd18a5a4ea58f0dcba5af1e91171b71c98419284a2c9ae","signature":"7dede46ecae5df128381d9b92e74e4fc8510f109d8e32b126e4892ee8ef99f00"},{"version":"2cbca994d27d599ebd7fe243fd253d43235a4a5a498a8611c8261b31866be183","signature":"aa4bee915f1d6ae1b4781e5620648fb9b2f7a9e2d7868010fe76128fc235b071"},{"version":"c0f563055650d22250a0fca44ceadb4f8a7a0f31b45fdea03c496b6ca503ef85","signature":"b1071c0c2c6de646ca8550508330b62c6f443a718f2a0f1b0829924552b6059d"},{"version":"ed203ab1bc9a065b558413f768f962c916c3aca033ea3df3a7949811e3e6e061","signature":"a58f5a5853963d990a3ead70f58f66cdda42ba6094c625797f944b69de8c68ff"},{"version":"a9dc12b357b9a5e91f825c61939a51a6328d386b5abdd2f3a74dc4d6c48e1849","signature":"3198c740e46ebee55d0707b68b4b1110bafd4199153e3cc9656269ffc0126bcf"},{"version":"ff16ee3197adfb61247951554886d2261b94c0b7895e5e08d83ae0fed84b1219","signature":"c8c204faea955067d75a6f450820a659f14b72daec929ba7bb8ce836026d78c8"},{"version":"763f52ce25f97d795c72f7476cfa66e08d2d21692a2710f8ff51dd18035dbf90","signature":"2b5bd0acb97c3ed3b5cb6ada9d83fd625489fff182655e6d3cd47c4babeada5d"},{"version":"894bd2a64bfce5b28d1f4b6760f2eb75f07ce64dd1c272bbf7ceb6fec3f27912","impliedFormat":99},{"version":"6c7176368037af28cb72f2392010fa1cef295d6d6744bca8cfb54985f3a18c3e","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab41ef1f2cdafb8df48be20cd969d875602483859dc194e9c97c8a576892c052","affectsGlobalScope":true,"impliedFormat":1},{"version":"437e20f2ba32abaeb7985e0afe0002de1917bc74e949ba585e49feba65da6ca1","affectsGlobalScope":true,"impliedFormat":1},{"version":"21d819c173c0cf7cc3ce57c3276e77fd9a8a01d35a06ad87158781515c9a438a","impliedFormat":1},{"version":"98cffbf06d6bab333473c70a893770dbe990783904002c4f1a960447b4b53dca","affectsGlobalScope":true,"impliedFormat":1},{"version":"3af97acf03cc97de58a3a4bc91f8f616408099bc4233f6d0852e72a8ffb91ac9","affectsGlobalScope":true,"impliedFormat":1},{"version":"808069bba06b6768b62fd22429b53362e7af342da4a236ed2d2e1c89fcca3b4a","affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e9c23ba78aabc2e0a27033f18737a6df754067731e69dc5f52823957d60a4b6","impliedFormat":1},{"version":"5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f","impliedFormat":1},{"version":"763fe0f42b3d79b440a9b6e51e9ba3f3f91352469c1e4b3b67bfa4ff6352f3f4","impliedFormat":1},{"version":"25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc","impliedFormat":1},{"version":"c464d66b20788266e5353b48dc4aa6bc0dc4a707276df1e7152ab0c9ae21fad8","impliedFormat":1},{"version":"78d0d27c130d35c60b5e5566c9f1e5be77caf39804636bc1a40133919a949f21","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"1d6e127068ea8e104a912e42fc0a110e2aa5a66a356a917a163e8cf9a65e4a75","impliedFormat":1},{"version":"5ded6427296cdf3b9542de4471d2aa8d3983671d4cac0f4bf9c637208d1ced43","impliedFormat":1},{"version":"7f182617db458e98fc18dfb272d40aa2fff3a353c44a89b2c0ccb3937709bfb5","impliedFormat":1},{"version":"cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd","impliedFormat":1},{"version":"385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20","impliedFormat":1},{"version":"9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219","impliedFormat":1},{"version":"0b8a9268adaf4da35e7fa830c8981cfa22adbbe5b3f6f5ab91f6658899e657a7","impliedFormat":1},{"version":"11396ed8a44c02ab9798b7dca436009f866e8dae3c9c25e8c1fbc396880bf1bb","impliedFormat":1},{"version":"ba7bc87d01492633cb5a0e5da8a4a42a1c86270e7b3d2dea5d156828a84e4882","impliedFormat":1},{"version":"4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd","impliedFormat":1},{"version":"c21dc52e277bcfc75fac0436ccb75c204f9e1b3fa5e12729670910639f27343e","impliedFormat":1},{"version":"13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9","impliedFormat":1},{"version":"9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a","impliedFormat":1},{"version":"4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da","impliedFormat":1},{"version":"24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2","impliedFormat":1},{"version":"ea0148f897b45a76544ae179784c95af1bd6721b8610af9ffa467a518a086a43","impliedFormat":1},{"version":"24c6a117721e606c9984335f71711877293a9651e44f59f3d21c1ea0856f9cc9","impliedFormat":1},{"version":"dd3273ead9fbde62a72949c97dbec2247ea08e0c6952e701a483d74ef92d6a17","impliedFormat":1},{"version":"405822be75ad3e4d162e07439bac80c6bcc6dbae1929e179cf467ec0b9ee4e2e","impliedFormat":1},{"version":"0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6","impliedFormat":1},{"version":"e61be3f894b41b7baa1fbd6a66893f2579bfad01d208b4ff61daef21493ef0a8","impliedFormat":1},{"version":"bd0532fd6556073727d28da0edfd1736417a3f9f394877b6d5ef6ad88fba1d1a","impliedFormat":1},{"version":"89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2","impliedFormat":1},{"version":"615ba88d0128ed16bf83ef8ccbb6aff05c3ee2db1cc0f89ab50a4939bfc1943f","impliedFormat":1},{"version":"a4d551dbf8746780194d550c88f26cf937caf8d56f102969a110cfaed4b06656","impliedFormat":1},{"version":"8bd86b8e8f6a6aa6c49b71e14c4ffe1211a0e97c80f08d2c8cc98838006e4b88","impliedFormat":1},{"version":"317e63deeb21ac07f3992f5b50cdca8338f10acd4fbb7257ebf56735bf52ab00","impliedFormat":1},{"version":"4732aec92b20fb28c5fe9ad99521fb59974289ed1e45aecb282616202184064f","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"bf67d53d168abc1298888693338cb82854bdb2e69ef83f8a0092093c2d562107","impliedFormat":1},{"version":"b52476feb4a0cbcb25e5931b930fc73cb6643fb1a5060bf8a3dda0eeae5b4b68","affectsGlobalScope":true,"impliedFormat":1},{"version":"f9501cc13ce624c72b61f12b3963e84fad210fbdf0ffbc4590e08460a3f04eba","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"0fa06ada475b910e2106c98c68b10483dc8811d0c14a8a8dd36efb2672485b29","impliedFormat":1},{"version":"33e5e9aba62c3193d10d1d33ae1fa75c46a1171cf76fef750777377d53b0303f","impliedFormat":1},{"version":"2b06b93fd01bcd49d1a6bd1f9b65ddcae6480b9a86e9061634d6f8e354c1468f","impliedFormat":1},{"version":"6a0cd27e5dc2cfbe039e731cf879d12b0e2dded06d1b1dedad07f7712de0d7f4","affectsGlobalScope":true,"impliedFormat":1},{"version":"13f5c844119c43e51ce777c509267f14d6aaf31eafb2c2b002ca35584cd13b29","impliedFormat":1},{"version":"e60477649d6ad21542bd2dc7e3d9ff6853d0797ba9f689ba2f6653818999c264","impliedFormat":1},{"version":"c2510f124c0293ab80b1777c44d80f812b75612f297b9857406468c0f4dafe29","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"4c829ab315f57c5442c6667b53769975acbf92003a66aef19bce151987675bd1","affectsGlobalScope":true,"impliedFormat":1},{"version":"b2ade7657e2db96d18315694789eff2ddd3d8aea7215b181f8a0b303277cc579","impliedFormat":1},{"version":"9855e02d837744303391e5623a531734443a5f8e6e8755e018c41d63ad797db2","impliedFormat":1},{"version":"4d631b81fa2f07a0e63a9a143d6a82c25c5f051298651a9b69176ba28930756d","impliedFormat":1},{"version":"836a356aae992ff3c28a0212e3eabcb76dd4b0cc06bcb9607aeef560661b860d","impliedFormat":1},{"version":"1e0d1f8b0adfa0b0330e028c7941b5a98c08b600efe7f14d2d2a00854fb2f393","impliedFormat":1},{"version":"41670ee38943d9cbb4924e436f56fc19ee94232bc96108562de1a734af20dc2c","affectsGlobalScope":true,"impliedFormat":1},{"version":"c906fb15bd2aabc9ed1e3f44eb6a8661199d6c320b3aa196b826121552cb3695","impliedFormat":1},{"version":"22295e8103f1d6d8ea4b5d6211e43421fe4564e34d0dd8e09e520e452d89e659","impliedFormat":1},{"version":"58647d85d0f722a1ce9de50955df60a7489f0593bf1a7015521efe901c06d770","impliedFormat":1},{"version":"73b5fa37db36eeac90c4d752e39586f1b57187400c4f5280fd05f16437287a45","impliedFormat":1},{"version":"a10f0e1854f3316d7ee437b79649e5a6ae3ae14ffe6322b02d4987071a95362e","impliedFormat":1},{"version":"e208f73ef6a980104304b0d2ca5f6bf1b85de6009d2c7e404028b875020fa8f2","impliedFormat":1},{"version":"d163b6bc2372b4f07260747cbc6c0a6405ab3fbcea3852305e98ac43ca59f5bc","impliedFormat":1},{"version":"e6fa9ad47c5f71ff733744a029d1dc472c618de53804eae08ffc243b936f87ff","affectsGlobalScope":true,"impliedFormat":1},{"version":"a6f137d651076822d4fe884287e68fd61785a0d3d1fdb250a5059b691fa897db","impliedFormat":1},{"version":"24826ed94a78d5c64bd857570fdbd96229ad41b5cb654c08d75a9845e3ab7dde","impliedFormat":1},{"version":"8b479a130ccb62e98f11f136d3ac80f2984fdc07616516d29881f3061f2dd472","impliedFormat":1},{"version":"928af3d90454bf656a52a48679f199f64c1435247d6189d1caf4c68f2eaf921f","affectsGlobalScope":true,"impliedFormat":1},{"version":"bceb58df66ab8fb00170df20cd813978c5ab84be1d285710c4eb005d8e9d8efb","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f16a7e4deafa527ed9995a772bb380eb7d3c2c0fd4ae178c5263ed18394db2c","impliedFormat":1},{"version":"933921f0bb0ec12ef45d1062a1fc0f27635318f4d294e4d99de9a5493e618ca2","impliedFormat":1},{"version":"71a0f3ad612c123b57239a7749770017ecfe6b66411488000aba83e4546fde25","impliedFormat":1},{"version":"77fbe5eecb6fac4b6242bbf6eebfc43e98ce5ccba8fa44e0ef6a95c945ff4d98","impliedFormat":1},{"version":"4f9d8ca0c417b67b69eeb54c7ca1bedd7b56034bb9bfd27c5d4f3bc4692daca7","impliedFormat":1},{"version":"814118df420c4e38fe5ae1b9a3bafb6e9c2aa40838e528cde908381867be6466","impliedFormat":1},{"version":"a3fc63c0d7b031693f665f5494412ba4b551fe644ededccc0ab5922401079c95","impliedFormat":1},{"version":"80523c00b8544a2000ae0143e4a90a00b47f99823eb7926c1e03c494216fc363","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","impliedFormat":1},{"version":"746911b62b329587939560deb5c036aca48aece03147b021fa680223255d5183","affectsGlobalScope":true,"impliedFormat":1},{"version":"18fd40412d102c5564136f29735e5d1c3b455b8a37f920da79561f1fde068208","impliedFormat":1},{"version":"c8d3e5a18ba35629954e48c4cc8f11dc88224650067a172685c736b27a34a4dc","impliedFormat":1},{"version":"f0be1b8078cd549d91f37c30c222c2a187ac1cf981d994fb476a1adc61387b14","affectsGlobalScope":true,"impliedFormat":1},{"version":"0aaed1d72199b01234152f7a60046bc947f1f37d78d182e9ae09c4289e06a592","impliedFormat":1},{"version":"2b55d426ff2b9087485e52ac4bc7cfafe1dc420fc76dad926cd46526567c501a","impliedFormat":1},{"version":"66ba1b2c3e3a3644a1011cd530fb444a96b1b2dfe2f5e837a002d41a1a799e60","impliedFormat":1},{"version":"7e514f5b852fdbc166b539fdd1f4e9114f29911592a5eb10a94bb3a13ccac3c4","impliedFormat":1},{"version":"5b7aa3c4c1a5d81b411e8cb302b45507fea9358d3569196b27eb1a27ae3a90ef","affectsGlobalScope":true,"impliedFormat":1},{"version":"5987a903da92c7462e0b35704ce7da94d7fdc4b89a984871c0e2b87a8aae9e69","affectsGlobalScope":true,"impliedFormat":1},{"version":"ea08a0345023ade2b47fbff5a76d0d0ed8bff10bc9d22b83f40858a8e941501c","impliedFormat":1},{"version":"47613031a5a31510831304405af561b0ffaedb734437c595256bb61a90f9311b","impliedFormat":1},{"version":"ae062ce7d9510060c5d7e7952ae379224fb3f8f2dd74e88959878af2057c143b","impliedFormat":1},{"version":"8a1a0d0a4a06a8d278947fcb66bf684f117bf147f89b06e50662d79a53be3e9f","affectsGlobalScope":true,"impliedFormat":1},{"version":"358765d5ea8afd285d4fd1532e78b88273f18cb3f87403a9b16fef61ac9fdcfe","impliedFormat":1},{"version":"9f55299850d4f0921e79b6bf344b47c420ce0f507b9dcf593e532b09ea7eeea1","impliedFormat":1},{"version":"0838507efff4f479c6f603ec812810ddfe14ab32abf8f4a8def140be970fe439","impliedFormat":99},{"version":"2612d12378afc08cbddaffce6d2cdee3c8ee1d79a6c819a417b9f1d9cf99d323","impliedFormat":99},{"version":"7212c2d58855b8df35275180e97903a4b6093d4fbaefea863d8d028da63938c6","impliedFormat":99},{"version":"5bd0f306b4a9dc65bccf38d9295bc52720d2fa455e06f604529d981b5eb8d9dc","impliedFormat":99},{"version":"f30992084e86f4b4c223c558b187cb0a9e83071592bd830d8ff2a471ee2bf2d4","impliedFormat":99},{"version":"65a0ce7fbc7b0d5f5bf243171d7bd7232a89a26d94b40fc66bc3605801d854df","impliedFormat":99},{"version":"dd9faff42b456b5f03b85d8fbd64838eb92f6f7b03b36322cbc59c005b7033d3","impliedFormat":99},{"version":"6ff702721d87c0ba8e7f8950e7b0a3b009dfd912fab3997e0b63fab8d83919c3","impliedFormat":99},{"version":"9dce9fc12e9a79d1135699d525aa6b44b71a45e32e3fa0cf331060b980b16317","impliedFormat":99},{"version":"586b2fd8a7d582329658aaceec22f8a5399e05013deb49bcfde28f95f093c8ee","impliedFormat":99},{"version":"dedc0ab5f7babe4aef870618cd2d4bc43dc67d1584ee43b68fc6e05554ef8f34","impliedFormat":99},{"version":"ef1f3eadd7bed282de45bafd7c2c00105cf1db93e22f6cd763bec8a9c2cf6df1","impliedFormat":99},{"version":"2b90463c902dbe4f5bbb9eae084c05de37477c17a5de1e342eb7cbc9410dc6a1","impliedFormat":99},{"version":"630223d569aada312a696b5e5fc01c0b694aa7d49abc5b166f1dfd6260a4b130","signature":"1e9de6404ed16aaebc8448875eccd716a386cfd0cdd5dadaae43f255dbb891d7"},{"version":"2cb544bd402989c3eee918d441d9b63f146b17054195b79b2f5333ab39c0fb8e","signature":"a8edc1b21c1f03680da647066e6220d1ce5eed8fb1829bd5aa520f3637e55b83"},{"version":"249eea78b532c9787d574ae71d097963cff1c8a66eced95845df5fe986dcc863","signature":"43e818adf60173644896298637f47b01d5819b17eda46eaa32d0c7d64724d012"},{"version":"fd48b2c2e7fcf355923abb312abc0b5e3af38336474cf0976cdf077bb39b1882","signature":"067b279103384302bd046eb886b04ec7fff7d1b4264bedf44050b9ebcd0e7013"}],"root":[60,184,185,[220,230],[350,353]],"options":{"allowSyntheticDefaultImports":true,"composite":true,"declaration":true,"declarationMap":true,"esModuleInterop":true,"module":7,"outDir":"./dist","rootDir":"./src","skipLibCheck":true,"sourceMap":true,"strict":true,"target":9},"referencedMap":[[218,1],[217,2],[196,3],[193,4],[192,5],[194,6],[195,5],[191,2],[283,7],[284,7],[285,8],[237,9],[286,10],[287,11],[288,12],[232,2],[235,13],[233,2],[234,2],[289,14],[290,15],[291,16],[292,17],[293,18],[294,19],[295,19],[296,20],[297,21],[298,22],[299,23],[238,2],[236,2],[300,24],[301,25],[302,26],[336,27],[303,28],[304,2],[305,29],[306,30],[307,31],[308,32],[309,33],[310,34],[311,35],[312,36],[313,37],[314,37],[315,38],[316,2],[317,39],[318,40],[320,41],[319,42],[321,43],[322,44],[323,45],[324,46],[325,47],[326,48],[327,49],[328,50],[329,51],[330,52],[331,53],[332,54],[333,55],[239,2],[240,2],[241,2],[280,56],[281,2],[282,2],[334,57],[335,58],[168,59],[169,60],[145,61],[148,62],[166,59],[167,59],[157,59],[156,63],[154,59],[149,59],[162,59],[160,59],[164,59],[144,59],[161,59],[165,59],[150,59],[151,59],[163,59],[146,59],[152,59],[153,59],[155,59],[159,59],[170,64],[158,59],[147,59],[183,65],[182,2],[177,64],[179,66],[178,64],[171,64],[172,64],[174,64],[176,64],[180,66],[181,66],[173,66],[175,66],[59,67],[58,2],[347,68],[348,69],[346,70],[349,71],[343,72],[344,73],[345,74],[337,2],[339,72],[340,72],[342,75],[341,72],[231,76],[338,77],[211,78],[189,79],[212,78],[213,80],[214,80],[200,2],[201,79],[202,79],[203,81],[204,81],[205,82],[206,82],[186,2],[207,82],[198,83],[208,79],[187,79],[209,82],[190,80],[197,84],[188,85],[215,86],[219,87],[199,88],[216,89],[210,90],[56,2],[57,2],[11,2],[10,2],[2,2],[12,2],[13,2],[14,2],[15,2],[16,2],[17,2],[18,2],[19,2],[3,2],[20,2],[21,2],[4,2],[22,2],[26,2],[23,2],[24,2],[25,2],[27,2],[28,2],[29,2],[5,2],[30,2],[31,2],[32,2],[33,2],[6,2],[37,2],[34,2],[35,2],[36,2],[38,2],[7,2],[39,2],[44,2],[45,2],[40,2],[41,2],[42,2],[43,2],[8,2],[49,2],[46,2],[47,2],[48,2],[50,2],[9,2],[51,2],[52,2],[53,2],[55,2],[54,2],[1,2],[258,91],[268,92],[257,91],[278,93],[249,94],[248,95],[277,96],[271,97],[276,98],[251,99],[265,100],[250,101],[274,102],[246,103],[245,96],[275,104],[247,105],[252,106],[253,2],[256,106],[243,2],[279,107],[269,108],[260,109],[261,110],[263,111],[259,112],[262,113],[272,96],[254,114],[255,115],[264,116],[244,117],[267,108],[266,106],[270,2],[273,118],[140,119],[134,120],[138,121],[135,121],[131,120],[139,122],[136,123],[137,121],[132,124],[133,125],[127,126],[68,127],[70,128],[126,2],[69,129],[130,130],[129,131],[128,132],[61,2],[71,127],[72,2],[63,133],[67,134],[62,2],[64,135],[65,136],[66,2],[73,137],[74,137],[75,137],[76,137],[77,137],[78,137],[79,137],[80,137],[81,137],[82,137],[83,137],[84,137],[85,137],[86,137],[88,137],[87,137],[89,137],[90,137],[91,137],[92,137],[93,137],[125,138],[94,137],[95,137],[96,137],[97,137],[98,137],[99,137],[100,137],[101,137],[102,137],[103,137],[104,137],[105,137],[106,137],[108,137],[107,137],[109,137],[110,137],[111,137],[112,137],[113,137],[114,137],[115,137],[116,137],[117,137],[118,137],[119,137],[120,137],[121,137],[124,137],[122,137],[123,137],[143,139],[141,2],[142,140],[352,141],[351,142],[60,2],[184,143],[185,144],[221,145],[350,146],[220,147],[222,148],[230,148],[228,142],[229,149],[353,150],[224,151],[226,152],[225,148],[227,152],[223,2],[242,2]],"latestChangedDtsFile":"./dist/core/utils.d.ts","version":"5.9.3"} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..66e801d --- /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" + ] +}