From 6c12e8708ea91b889cee2bed0c16ab3b8c680b64 Mon Sep 17 00:00:00 2001 From: Steven McClankerton Date: Thu, 3 Sep 2026 09:51:29 +0000 Subject: [PATCH 1/6] Replace the chained release workflows with a single release pipeline The six numbered workflows chained by workflow_run and passed state between each other through bot commits. Publishing now happens in one workflow, release.yml, for both the insider and the stable channel: plan -> test -> publish-language-server / package -> marketplaces. The next extension version is derived from the git release tags (x.y.z for stable, insider/x.y.z for insider, one shared counter) instead of a committed version file, so a release no longer creates version-bump commits. The plan job resolves one commit SHA that every later job checks out, and the vsix is built once and passed on as a workflow artifact. check_for_prisma_update.yml replaces 1_check_for_updates.yml: it records new Prisma CLI versions and dispatches release.yml per channel. Its cron trigger stays commented out while ORM iteration is paused. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01YJFVnTXe5iAtFHpARAg4ZU --- .github/workflows/1_2_bump_extension_only.yml | 72 ----- .../1_2_stable_extension_release.yml | 66 ---- .github/workflows/1_check_for_updates.yml | 59 ---- .github/workflows/2_bump_versions.yml | 97 ------ .github/workflows/3_LS_tests_publish.yml | 94 ------ .github/workflows/4_e2e_tests.yml | 79 ----- .github/workflows/5_build.yml | 103 ------ .github/workflows/6_publish.yml | 84 ----- .github/workflows/check_for_prisma_update.yml | 79 +++++ .github/workflows/release.yml | 293 ++++++++++++++++++ docs/ci-cd.md | 100 ++++-- .../__tests__/next-extension-version.test.mjs | 84 +++++ scripts/next_extension_version.mjs | 80 +++++ 13 files changed, 610 insertions(+), 680 deletions(-) delete mode 100644 .github/workflows/1_2_bump_extension_only.yml delete mode 100644 .github/workflows/1_2_stable_extension_release.yml delete mode 100644 .github/workflows/1_check_for_updates.yml delete mode 100644 .github/workflows/2_bump_versions.yml delete mode 100644 .github/workflows/3_LS_tests_publish.yml delete mode 100644 .github/workflows/4_e2e_tests.yml delete mode 100644 .github/workflows/5_build.yml delete mode 100644 .github/workflows/6_publish.yml create mode 100644 .github/workflows/check_for_prisma_update.yml create mode 100644 .github/workflows/release.yml create mode 100644 scripts/__tests__/next-extension-version.test.mjs create mode 100644 scripts/next_extension_version.mjs diff --git a/.github/workflows/1_2_bump_extension_only.yml b/.github/workflows/1_2_bump_extension_only.yml deleted file mode 100644 index 1a2405333e..0000000000 --- a/.github/workflows/1_2_bump_extension_only.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: 1/2. Bump versions for extension only (on push to main and patch branch) - -# This workflow covers both '1_check_for_updates' and '2_bump_versions' workflows, therefore it is another variant of the same, but specifically for extension only, without any Prisma CLI updates that would need to be handled. - -on: - push: - branches: - - 'main' - - '**.x' - - '!renovate/**' - paths-ignore: - - '.github/**' - workflow_dispatch: - -concurrency: - group: version-management - cancel-in-progress: false - -env: - ENVIRONMENT: ${{ secrets.ENVIRONMENT }} - PRISMA_TELEMETRY_INFORMATION: 'language-tools 1_2_bump_extension_only.yml' - -jobs: - bump: - runs-on: ubuntu-latest - timeout-minutes: 7 - steps: - - uses: actions/checkout@v4 - with: - token: ${{ secrets.PRISMA_BOT_TOKEN }} - - name: Install pnpm - uses: pnpm/action-setup@v4 - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'pnpm' - - name: checkout main if necessary - if: endsWith(github.ref, '/main') == false - run: git fetch && git checkout main - - name: Install Dependencies - run: pnpm install - shell: bash - - name: Setup Git - run: | - sh scripts/set_git_credentials.sh - - name: Update extension version in scripts folder - id: update - run: node scripts/bump_extension_version.mjs extension-patch-release - - name: Commit and Push - run: | - sh scripts/commit_and_push.sh "[skip ci] bump extension version in scripts to ${{ steps.update.outputs.next_extension_version }}." - - - name: Checkout previous branch - if: endsWith(github.ref, '/main') == false - run: git checkout -f ${{ github.ref_name }} - - name: Update VS Code extension version - run: | - node scripts/update_package_json_files.mjs dev ${{ steps.update.outputs.next_extension_version }} "" - - name: Install Dependencies - run: pnpm install - - name: Commit and Push - run: | - sh scripts/commit_and_push.sh "[skip ci] bump extension version in package.json to ${{ steps.update.outputs.next_extension_version }}." ${{ steps.setup_branch.outputs.new_branch }} - - - name: Trigger next workflow for Language Server tests - if: endsWith(github.ref, '/main') - uses: benc-uk/workflow-dispatch@v1 - with: - workflow: 3. Test Language Server and publish - token: ${{ secrets.PRISMA_BOT_TOKEN }} - inputs: '{ "release_channel": "insider", "extension_version": "${{ steps.update.outputs.next_extension_version }}", "branch": "main", "trigger_reason": "Commit from ${{ github.ref_name }}" }' diff --git a/.github/workflows/1_2_stable_extension_release.yml b/.github/workflows/1_2_stable_extension_release.yml deleted file mode 100644 index 1a5a963c3f..0000000000 --- a/.github/workflows/1_2_stable_extension_release.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: 1/2. Bump and release a stable version of the extension, meant for manual releases - -# This workflow covers both '1_check_for_updates' and '2_bump_versions' workflows, therefore it is another variant of the same, but specifically for extension only, without any Prisma CLI updates that would need to be handled. - -on: - workflow_dispatch: - inputs: - release_type: - description: 'Type of release to perform' - required: true - type: choice - options: - - patch - - minor - - major - -concurrency: - group: version-management - cancel-in-progress: false - -env: - ENVIRONMENT: ${{ secrets.ENVIRONMENT }} - PRISMA_TELEMETRY_INFORMATION: 'language-tools 1_2_stable_extension_release.yml' - -jobs: - bump: - runs-on: ubuntu-latest - timeout-minutes: 7 - steps: - - uses: actions/checkout@v4 - with: - token: ${{ secrets.PRISMA_BOT_TOKEN }} - - name: Install pnpm - uses: pnpm/action-setup@v4 - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'pnpm' - - name: Install Dependencies - run: pnpm install - shell: bash - - name: Setup Git - run: | - sh scripts/set_git_credentials.sh - - name: Update extension version in scripts folder - id: update - run: node scripts/bump_extension_version.mjs extension-${{ github.event.inputs.release_type }}-release - - name: Commit and Push - run: | - sh scripts/commit_and_push.sh "[skip ci] bump extension version in scripts to ${{ steps.update.outputs.next_extension_version }}." - - name: Update VS Code extension version - run: | - node scripts/update_package_json_files.mjs latest ${{ steps.update.outputs.next_extension_version }} "" - - name: Install Dependencies - run: pnpm install - - name: Commit and Push - run: | - sh scripts/commit_and_push.sh "[skip ci] bump extension version in package.json to ${{ steps.update.outputs.next_extension_version }}." - - - name: Trigger next workflow for Language Server tests - uses: benc-uk/workflow-dispatch@v1 - with: - workflow: 3. Test Language Server and publish - token: ${{ secrets.PRISMA_BOT_TOKEN }} - inputs: '{ "release_channel": "stable", "extension_version": "${{ steps.update.outputs.next_extension_version }}", "branch": "main", "trigger_reason": "Manual ${{ github.event.inputs.release_type }} stable extension release" }' diff --git a/.github/workflows/1_check_for_updates.yml b/.github/workflows/1_check_for_updates.yml deleted file mode 100644 index 0f54ed2c78..0000000000 --- a/.github/workflows/1_check_for_updates.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: 1. Check for Prisma CLI Update - -on: - # Scheduled trigger disabled: ORM iteration is paused; CLI-update polling - # runs on demand. Restore the `schedule:` block below to re-enable. - # schedule: - # - cron: '*/5 * * * *' - workflow_dispatch: - -env: - ENVIRONMENT: ${{ secrets.ENVIRONMENT }} - PRISMA_TELEMETRY_INFORMATION: 'language-tools 1_check_for_update.yml' - -jobs: - check: - name: Check for Prisma CLI update - runs-on: ubuntu-latest - timeout-minutes: 7 - steps: - - uses: actions/checkout@v4 - with: - token: ${{ secrets.PRISMA_BOT_TOKEN }} - - name: Install pnpm - uses: pnpm/action-setup@v4 - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'pnpm' - - name: Install Dependencies - run: pnpm install - - # First it runs this - - name: Check for Prisma CLI update - id: check_update - run: node scripts/check_for_update.mjs - - # ... and depending on the output any (or none) of these 3: - - name: Invoke bump workflow with inputs for dev - if: ${{ steps.check_update.outputs.dev_version }} - uses: benc-uk/workflow-dispatch@v1 - with: - workflow: 2. Bump versions - token: ${{ secrets.PRISMA_BOT_TOKEN }} - inputs: '{ "npm_channel": "dev", "version": "${{ steps.check_update.outputs.dev_version }}" }' - - name: Invoke bump workflow with inputs for latest - if: ${{ steps.check_update.outputs.latest_version }} - uses: benc-uk/workflow-dispatch@v1 - with: - workflow: 2. Bump versions - token: ${{ secrets.PRISMA_BOT_TOKEN }} - inputs: '{ "npm_channel": "latest", "version": "${{ steps.check_update.outputs.latest_version }}" }' - - name: Invoke bump workflow with inputs for patch-dev - if: ${{ steps.check_update.outputs.patch-dev_version }} - uses: benc-uk/workflow-dispatch@v1 - with: - workflow: 2. Bump versions - token: ${{ secrets.PRISMA_BOT_TOKEN }} - inputs: '{ "npm_channel": "patch-dev", "version": "${{ steps.check_update.outputs.patch-dev_version }}" }' diff --git a/.github/workflows/2_bump_versions.yml b/.github/workflows/2_bump_versions.yml deleted file mode 100644 index 83e0e0b6b9..0000000000 --- a/.github/workflows/2_bump_versions.yml +++ /dev/null @@ -1,97 +0,0 @@ -name: 2. Bump versions -run-name: 2. Bump versions - CLI ${{ inputs.version }} from ${{ inputs.npm_channel }} - -on: - workflow_dispatch: - inputs: - npm_channel: - description: 'Prisma CLI NPM Channel to bump' - required: true - type: choice - options: - - latest - - dev - - patch-dev - version: - description: 'New Prisma CLI version in that channel' - required: true - -concurrency: - group: version-management - cancel-in-progress: false - -env: - ENVIRONMENT: ${{ secrets.ENVIRONMENT }} - PRISMA_TELEMETRY_INFORMATION: 'language-tools 2_bump_versions.yml' - RELEASE_CHANNEL: ${{ github.event.inputs.npm_channel == 'latest' && 'stable' || 'insider' }} - -jobs: - bump: - runs-on: ubuntu-latest - timeout-minutes: 7 - steps: - - uses: actions/checkout@v4 - with: - token: ${{ secrets.PRISMA_BOT_TOKEN }} - - name: Install pnpm - uses: pnpm/action-setup@v4 - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'pnpm' - - name: Install Dependencies - run: pnpm install - - name: Setup Git - run: | - sh scripts/set_git_credentials.sh - - name: Print inputs - run: | - echo ${{github.event.inputs.npm_channel}} - echo ${{github.event.inputs.version}} - - - name: Update versions file in scripts folder - run: | - echo "${{ github.event.inputs.version }}" >scripts/versions/prisma_${{ github.event.inputs.npm_channel }} - - if git diff --exit-code; then - echo "Version has not changed! We should not attempt to bump." - exit 1 - fi - - - name: Update next extension version in scripts folder and output for later use - id: update # Used to access the calculated next extension version in later steps - run: node scripts/bump_extension_version.mjs prisma-${{ github.event.inputs.npm_channel }}-release - - name: Commit and Push - run: | - sh scripts/commit_and_push.sh "[skip ci] bump Prisma CLI ${{ github.event.inputs.npm_channel }} version to ${{ github.event.inputs.version }} and extension version to ${{ steps.update.outputs.next_extension_version }} in scripts." - - - name: Switch branch - id: setup_branch - run: | - sh scripts/setup_branch.sh ${{ github.event.inputs.npm_channel }} - - name: Check for minor or major release - id: is_minor_or_major - run: | - VERSION_SCRIPT=$([ -f scripts/is_minor_or_major_release.mjs ] && echo "is_minor_or_major_release.mjs" || echo "is_minor_or_major_release.js") - node "scripts/$VERSION_SCRIPT" ${{ github.event.inputs.npm_channel }} - - name: Reset on 'main' branch if minor or major release - if: ${{steps.is_minor_or_major.outputs.is_minor_or_major_release}} - run: | - git reset --hard main - git push --force origin stable - - name: Update Prisma CLI version and VS Code extension version - run: | - node scripts/update_package_json_files.mjs ${{ github.event.inputs.npm_channel }} ${{ steps.update.outputs.next_extension_version }} ${{ github.event.inputs.version }} - - name: Install Dependencies to update lock file - run: pnpm install --frozen-lockfile=false - - name: Push to branch - run: | - sh scripts/commit_and_push.sh "[skip ci] bump extension version to ${{ steps.update.outputs.next_extension_version }} in package.json" ${{ steps.setup_branch.outputs.branch}} ${{ steps.setup_branch.outputs.new_branch }} - - - name: Trigger next workflow for Language Server tests - uses: benc-uk/workflow-dispatch@v1 - with: - workflow: 3. Test Language Server and publish - token: ${{ secrets.PRISMA_BOT_TOKEN }} - inputs: '{ "release_channel": "${{ env.RELEASE_CHANNEL }}", "extension_version": "${{ steps.update.outputs.next_extension_version }}", "branch": "${{steps.setup_branch.outputs.branch}}", "trigger_reason": "Prisma CLI version ${{github.event.inputs.version}}" }' diff --git a/.github/workflows/3_LS_tests_publish.yml b/.github/workflows/3_LS_tests_publish.yml deleted file mode 100644 index 61f894fa12..0000000000 --- a/.github/workflows/3_LS_tests_publish.yml +++ /dev/null @@ -1,94 +0,0 @@ -name: 3. Test Language Server and publish -run-name: 3. Test Language Server and publish - ${{ inputs.trigger_reason }} - -on: - workflow_dispatch: - inputs: - release_channel: - description: "Identifies whether we're publishing an insider or stable release of the extension" - required: true - type: choice - options: - - insider - - stable - branch: - description: 'Branch to run run this workflow on' - required: true - extension_version: - description: 'VS Code extension version. When provided, it will trigger the next workflow to bump the LS version in the extension.' - trigger_reason: - description: 'Information about what caused the publishing process in the first place.' - -permissions: - id-token: write # required for OIDC / Trusted Publishers - -env: - PRISMA_TELEMETRY_INFORMATION: 'language-tools 3_LS_unit_tests_publish.yml' - LS_NPM_CHANNEL: ${{ github.event.inputs.release_channel == 'stable' && 'latest' || 'dev' }} - -jobs: - tests: - name: Run tests - runs-on: ${{ matrix.os }} - timeout-minutes: 10 - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - steps: - - uses: actions/checkout@v4 - with: - token: ${{ secrets.PRISMA_BOT_TOKEN }} - ref: ${{ github.event.inputs.branch }} - - name: Install pnpm - uses: pnpm/action-setup@v4 - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'pnpm' - - name: Install Dependencies - run: pnpm install - - name: Build Language Server - run: pnpm --filter @prisma/language-server build - - name: Run Language Server tests - run: pnpm --filter @prisma/language-server test - - bump: - name: Bump Language Server version - needs: [tests] - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.inputs.branch }} - - name: Install pnpm - uses: pnpm/action-setup@v4 - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'pnpm' - - name: Update npm to support Trusted Publishers - run: npm install -g npm@^11.5 - - name: Print inputs - run: | - echo ${{github.event.inputs.release_channel}} - echo ${{github.event.inputs.extension_version || 'not provided'}} - echo ${{github.event.inputs.branch}} - echo ${{github.event.inputs.trigger_reason || 'not provided'}} - - name: Install Dependencies - run: pnpm install - - name: Typecheck Language Server - run: pnpm --filter @prisma/language-server typecheck - - name: Build Language Server - run: pnpm --filter @prisma/language-server build - - name: Publish Language Server to npm - run: pnpm --filter @prisma/language-server publish --tag ${{ env.LS_NPM_CHANNEL }} --access public --no-git-checks - - name: Trigger next workflow for VS Code e2e tests - if: ${{ github.event.inputs.extension_version != '' }} - uses: benc-uk/workflow-dispatch@v1 - with: - workflow: 4. E2E tests VS Code Extension - token: ${{ secrets.PRISMA_BOT_TOKEN }} - inputs: '{ "release_channel": "${{ github.event.inputs.release_channel }}", "extension_version": "${{ github.event.inputs.extension_version }}", "branch": "${{ github.event.inputs.branch }}", "trigger_reason": "${{github.event.inputs.trigger_reason}}" }' diff --git a/.github/workflows/4_e2e_tests.yml b/.github/workflows/4_e2e_tests.yml deleted file mode 100644 index d5ffa6ad47..0000000000 --- a/.github/workflows/4_e2e_tests.yml +++ /dev/null @@ -1,79 +0,0 @@ -name: 4. E2E tests VS Code Extension -run-name: 4. E2E tests VS Code Extension - ${{ inputs.trigger_reason }} - -on: - workflow_dispatch: - inputs: - release_channel: - description: "Identifies whether we're publishing an insider or stable release of the extension" - required: true - type: choice - options: - - insider - - stable - extension_version: - description: 'VS Code extension version' - required: true - branch: - description: 'Branch to run run this workflow on' - required: true - trigger_reason: - description: 'Information about what caused the publishing process in the first place.' - required: true -env: - ENVIRONMENT: ${{ secrets.ENVIRONMENT }} - PRISMA_TELEMETRY_INFORMATION: 'language-tools 4_e2e_tests.yml' - -jobs: - tests: - name: E2E tests VS Code Extension - runs-on: ${{ matrix.os }} - timeout-minutes: 10 - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.inputs.branch }} - - name: Install pnpm - uses: pnpm/action-setup@v4 - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: '22' - cache: 'pnpm' - - name: Install Dependencies - run: pnpm install - - name: Build - run: pnpm build - - name: Headless E2E tests - uses: coactions/setup-xvfb@v1 - with: - run: pnpm test:e2e - - trigger: - name: Trigger next workflow - needs: [tests] - runs-on: ubuntu-latest - timeout-minutes: 7 - if: always() - steps: - - uses: actions/checkout@v4 - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - name: Print inputs - run: | - echo ${{github.event.inputs.release_channel}} - echo ${{github.event.inputs.extension_version}} - echo ${{github.event.inputs.branch}} - echo ${{github.event.inputs.trigger_reason}} - - name: Trigger next workflow to build extension - if: ${{ needs.tests.result == 'success' }} - uses: benc-uk/workflow-dispatch@v1 - with: - workflow: 5. Build extension - token: ${{ secrets.PRISMA_BOT_TOKEN }} - inputs: '{ "release_channel": "${{ github.event.inputs.release_channel }}", "extension_version": "${{ github.event.inputs.extension_version }}", "branch": "${{github.event.inputs.branch}}", "trigger_reason": "${{github.event.inputs.trigger_reason}}" }' diff --git a/.github/workflows/5_build.yml b/.github/workflows/5_build.yml deleted file mode 100644 index 473b9407ef..0000000000 --- a/.github/workflows/5_build.yml +++ /dev/null @@ -1,103 +0,0 @@ -name: 5. Build extension # and upload as release to GitHub -run-name: 5. Build extension + upload - ${{ inputs.trigger_reason }} - -on: - workflow_dispatch: - inputs: - release_channel: - description: "Identifies whether we're publishing an insider or stable release of the extension" - required: true - type: choice - options: - - insider - - stable - extension_version: - description: 'VS Code extension version' - required: true - branch: - description: 'Branch to run run this workflow on' - required: true - trigger_reason: - description: 'Information about what caused the publishing process in the first place.' - required: true - -env: - ENVIRONMENT: ${{ secrets.ENVIRONMENT }} - PRISMA_TELEMETRY_INFORMATION: 'language-tools 5_build.yml' - -jobs: - build: - name: Build extension - runs-on: ubuntu-latest - timeout-minutes: 7 - outputs: - tag_name: ${{ steps.names.outputs.tag_name }} - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.inputs.branch }} - token: ${{ secrets.PRISMA_BOT_TOKEN }} - - name: Install pnpm - uses: pnpm/action-setup@v4 - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'pnpm' - - name: Print inputs - run: | - echo ${{github.event.inputs.release_channel}} - echo ${{github.event.inputs.extension_version}} - echo ${{github.event.inputs.branch}} - echo ${{github.event.inputs.trigger_reason}} - - # Build the extension - - name: Install Dependencies - run: pnpm install - - name: Typecheck all packages - run: pnpm typecheck - - name: Build all packages - run: pnpm build - - name: Replace Readme for marketplace - run: node scripts/change_readme.mjs ${{ github.event.inputs.release_channel }} - - name: Build vsix - run: cd packages/vscode && pnpm package - - # Create GitHub Release and upload built result as asset - - name: Get git tag and asset name - id: names - run: node scripts/get_tag_name.mjs ${{ github.event.inputs.release_channel }} ${{ github.event.inputs.extension_version }} - - name: create a GitHub release - id: create_release - # This action is not maintained since 3+ years! - # see https://github.com/actions/create-release/issues/119 - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ steps.names.outputs.tag_name }} - release_name: ${{ steps.names.outputs.tag_name }} - - name: Get asset name for release asset - id: get_asset_name - run: | - echo "asset_name=${{ steps.names.outputs.asset_name }}-${{ github.event.inputs.extension_version }}.vsix" >> "$GITHUB_OUTPUT" - - - name: Upload Release Asset - id: upload-release-asset - # This action is not maintained since 3+ years! - # see https://github.com/actions/upload-release-asset/issues/78 - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ${{github.workspace}}/packages/vscode/${{ steps.get_asset_name.outputs.asset_name }} - asset_name: ${{ steps.get_asset_name.outputs.asset_name }} - asset_content_type: application/zip - - - name: Trigger next workflow to publish extension - uses: benc-uk/workflow-dispatch@v1 - with: - workflow: 6. Publish - token: ${{ secrets.PRISMA_BOT_TOKEN }} - inputs: '{ "asset_name": "${{ steps.get_asset_name.outputs.asset_name }}", "tag_name": "${{ steps.names.outputs.tag_name }}", "trigger_reason": "${{github.event.inputs.trigger_reason}}" }' diff --git a/.github/workflows/6_publish.yml b/.github/workflows/6_publish.yml deleted file mode 100644 index 1a72819fca..0000000000 --- a/.github/workflows/6_publish.yml +++ /dev/null @@ -1,84 +0,0 @@ -name: 6. Publish -run-name: 6. Publish - ${{ inputs.trigger_reason }} - -on: - workflow_dispatch: - inputs: - asset_name: - description: 'Name of the .vsix file in the release' - required: true - tag_name: - description: 'The release version/tag to fetch from.' - required: true - trigger_reason: - description: 'Information about what caused the publishing process in the first place.' - required: true - -env: - ENVIRONMENT: ${{ secrets.ENVIRONMENT }} - PRISMA_TELEMETRY_INFORMATION: 'language-tools 6_publish.yml' - -jobs: - marketplace: - name: Publish to marketplace - runs-on: ubuntu-latest - timeout-minutes: 7 - steps: - - uses: actions/checkout@v4 - with: - token: ${{ secrets.PRISMA_BOT_TOKEN }} - - name: Install pnpm - uses: pnpm/action-setup@v4 - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'pnpm' - - name: Print inputs - run: | - echo ${{github.event.inputs.asset_name}} - echo ${{github.event.inputs.tag_name}} - echo ${{github.event.inputs.trigger_reason}} - - name: Install Dependencies - run: pnpm install - - # Download Asset from GH release and publish to VS Code marketplace - - uses: dsaltares/fetch-gh-release-asset@master - with: - repo: 'prisma/language-tools' - version: 'tags/${{ github.event.inputs.tag_name }}' - file: ${{ github.event.inputs.asset_name }} - token: ${{ secrets.PRISMA_BOT_TOKEN }} - - - name: publish vsix to marketplace - run: cd packages/vscode && npx vsce publish --pat ${{ secrets.AZURE_DEVOPS_PERSONAL_ACCESS_TOKEN}} --packagePath ${{github.workspace}}/${{ github.event.inputs.asset_name }} - - open-vsx: - name: Publish to open-vsx - runs-on: ubuntu-latest - timeout-minutes: 7 - steps: - - uses: actions/checkout@v4 - - name: Install pnpm - uses: pnpm/action-setup@v4 - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'pnpm' - - name: Print inputs - run: | - echo ${{github.event.inputs.trigger_reason}} - - name: Install Dependencies - run: pnpm install - - # Download Asset from GH release and publish to open-vsx - - uses: dsaltares/fetch-gh-release-asset@master - with: - repo: 'prisma/language-tools' - version: 'tags/${{ github.event.inputs.tag_name }}' - file: ${{ github.event.inputs.asset_name }} - token: ${{ secrets.PRISMA_BOT_TOKEN }} - - - name: Publish vsix to open-vsx.org - run: cd packages/vscode && npx ovsx --debug publish ${{github.workspace}}/${{ github.event.inputs.asset_name }} --pat ${{ secrets.OPEN_VSX_ACCESS_TOKEN }} diff --git a/.github/workflows/check_for_prisma_update.yml b/.github/workflows/check_for_prisma_update.yml new file mode 100644 index 0000000000..05e4935468 --- /dev/null +++ b/.github/workflows/check_for_prisma_update.yml @@ -0,0 +1,79 @@ +name: Check for Prisma CLI update + +# Polls npm for new Prisma CLI versions and starts the Release workflow +# (release.yml) for every channel that has a new version: +# +# - dev -> insider release from main +# - latest -> stable release from the stable branch +# - patch-dev -> insider release from the x.y.x patch branch + +on: + # Scheduled trigger disabled: ORM iteration is paused; CLI-update polling + # runs on demand. Restore the `schedule:` block below to re-enable. + # schedule: + # - cron: '*/5 * * * *' + workflow_dispatch: + +env: + ENVIRONMENT: ${{ secrets.ENVIRONMENT }} + PRISMA_TELEMETRY_INFORMATION: 'language-tools check_for_prisma_update.yml' + +jobs: + check: + name: Check for Prisma CLI update + if: github.repository == 'prisma/language-tools' + runs-on: ubuntu-latest + timeout-minutes: 7 + env: + GH_TOKEN: ${{ secrets.PRISMA_BOT_TOKEN }} + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.PRISMA_BOT_TOKEN }} + fetch-depth: 0 # patch branches are created from release tags + - name: Install pnpm + uses: pnpm/action-setup@v4 + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'pnpm' + - name: Install Dependencies + run: pnpm install + + - name: Check for Prisma CLI update + id: check_update + run: node scripts/check_for_update.mjs + + - name: Record new versions + if: steps.check_update.outputs.dev_version || steps.check_update.outputs.latest_version || steps.check_update.outputs.patch-dev_version + env: + DEV_VERSION: ${{ steps.check_update.outputs.dev_version }} + LATEST_VERSION: ${{ steps.check_update.outputs.latest_version }} + PATCH_DEV_VERSION: ${{ steps.check_update.outputs.patch-dev_version }} + run: | + sh scripts/set_git_credentials.sh + if [ -n "$DEV_VERSION" ]; then echo "$DEV_VERSION" > scripts/versions/prisma_dev; fi + if [ -n "$LATEST_VERSION" ]; then echo "$LATEST_VERSION" > scripts/versions/prisma_latest; fi + if [ -n "$PATCH_DEV_VERSION" ]; then echo "$PATCH_DEV_VERSION" > scripts/versions/prisma_patch-dev; fi + git commit -am "[skip ci] record new Prisma CLI versions" + git push + + - name: Release insider (Prisma dev) + if: steps.check_update.outputs.dev_version + run: gh workflow run release.yml --ref main -f channel=insider -f prisma_version="${{ steps.check_update.outputs.dev_version }}" + - name: Release stable (Prisma latest) + if: steps.check_update.outputs.latest_version + run: gh workflow run release.yml --ref main -f channel=stable -f prisma_version="${{ steps.check_update.outputs.latest_version }}" + - name: Release insider from patch branch (Prisma patch-dev) + if: steps.check_update.outputs.patch-dev_version + env: + PATCH_DEV_VERSION: ${{ steps.check_update.outputs.patch-dev_version }} + run: | + BRANCH=$(node scripts/setup_branch.mjs patch-dev) + if [ -z "$(git ls-remote --heads origin "$BRANCH")" ]; then + # New patch branch: base it on the last marketplace-tested stable release + git branch "$BRANCH" "$(cat scripts/versions/tested_extension_stable)" + git push origin "$BRANCH" + fi + gh workflow run release.yml --ref main -f channel=insider -f ref="$BRANCH" -f prisma_version="$PATCH_DEV_VERSION" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..a552fbc878 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,293 @@ +name: Release +run-name: "Release ${{ github.event_name == 'push' && 'insider (push to main)' || format('{0}{1}', inputs.channel, inputs.prisma_version != '' && format(' — Prisma CLI {0}', inputs.prisma_version) || '') }}" + +# The single publishing pipeline for both the insider and the stable extension. +# +# - A push to main publishes an insider release. +# - A manual dispatch publishes an insider or stable release, optionally +# bumping the Prisma CLI dependencies first (`prisma_version`), optionally +# from another branch (`ref`, e.g. an x.y.x patch branch). +# - check_for_prisma_update.yml dispatches this workflow when a new Prisma CLI +# version is published to npm. +# +# The next extension version is derived from the git release tags (`x.y.z` for +# stable, `insider/x.y.z` for insider), so a release only creates a commit when +# it actually changes dependencies (`prisma_version` given). All other jobs +# check out the exact commit resolved by the `plan` job. + +on: + push: + branches: + - main + paths-ignore: + - '.github/**' + workflow_dispatch: + inputs: + channel: + description: 'Release channel' + required: true + type: choice + options: + - insider + - stable + default: insider + ref: + description: 'Branch to release from. Defaults to main for insider and stable for stable. Use an x.y.x branch to patch an older version.' + required: false + bump: + description: "Extension version bump (stable only; 'auto' derives it from the Prisma CLI version)" + required: false + type: choice + options: + - auto + - patch + - minor + - major + default: auto + prisma_version: + description: 'Bump the Prisma CLI dependencies to this version before releasing (creates one commit on the release branch)' + required: false + +concurrency: + group: release + cancel-in-progress: false + +permissions: + contents: write # push version tags and create GitHub releases + id-token: write # required for OIDC / npm Trusted Publishers + +env: + ENVIRONMENT: ${{ secrets.ENVIRONMENT }} + PRISMA_TELEMETRY_INFORMATION: 'language-tools release.yml' + NODE_VERSION: '22' + +jobs: + plan: + name: Plan release + if: github.repository == 'prisma/language-tools' + runs-on: ubuntu-latest + timeout-minutes: 10 + outputs: + channel: ${{ steps.params.outputs.channel }} + ref: ${{ steps.params.outputs.ref }} + sha: ${{ steps.sha.outputs.sha }} + version: ${{ steps.version.outputs.version }} + tag_name: ${{ steps.version.outputs.tag_name }} + asset_name: ${{ steps.version.outputs.asset_name }} + npm_channel: ${{ steps.version.outputs.npm_channel }} + ls_npm_tag: ${{ steps.version.outputs.ls_npm_tag }} + steps: + - name: Resolve channel and branch + id: params + run: | + if [ "${{ github.event_name }}" = "push" ]; then + CHANNEL=insider + REF="${{ github.sha }}" + else + CHANNEL="${{ inputs.channel }}" + REF="${{ inputs.ref }}" + if [ -z "$REF" ]; then + if [ "$CHANNEL" = "stable" ]; then REF=stable; else REF=main; fi + fi + fi + { + echo "channel=$CHANNEL" + echo "ref=$REF" + } >> "$GITHUB_OUTPUT" + - uses: actions/checkout@v4 + with: + ref: ${{ steps.params.outputs.ref }} + fetch-depth: 0 # all branches and tags: the next version is derived from release tags + token: ${{ secrets.PRISMA_BOT_TOKEN }} + - name: Install pnpm + uses: pnpm/action-setup@v4 + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'pnpm' + - name: Install Dependencies + run: pnpm install + - name: Compute next extension version + id: version + run: node scripts/next_extension_version.mjs "${{ steps.params.outputs.channel }}" "${{ inputs.bump || 'auto' }}" "${{ inputs.prisma_version }}" + - name: Reset stable branch to main (new Prisma minor/major) + if: steps.params.outputs.channel == 'stable' && inputs.prisma_version != '' && steps.version.outputs.release_type != 'patch' + run: | + git checkout -B stable origin/main + git push --force origin stable + - name: Bump Prisma dependencies + if: inputs.prisma_version != '' + run: | + node scripts/update_package_json_files.mjs "${{ steps.version.outputs.npm_channel }}" "${{ steps.version.outputs.version }}" "${{ inputs.prisma_version }}" + sh scripts/set_git_credentials.sh + git commit -am "[skip ci] bump Prisma CLI to ${{ inputs.prisma_version }} and extension to ${{ steps.version.outputs.version }}" + git push origin "HEAD:${{ steps.params.outputs.ref }}" + - name: Resolve release commit + id: sha + run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + + test: + name: Test (${{ matrix.os }}) + needs: [plan] + runs-on: ${{ matrix.os }} + timeout-minutes: 25 + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.plan.outputs.sha }} + - name: Install pnpm + uses: pnpm/action-setup@v4 + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'pnpm' + - name: Install Dependencies + run: pnpm install + - name: Build all packages + run: pnpm build + - name: Typecheck all packages + run: pnpm typecheck + - name: Run Language Server tests + run: pnpm --filter @prisma/language-server test + - name: Headless E2E tests + uses: coactions/setup-xvfb@v1 + with: + run: pnpm test:e2e + + publish-language-server: + name: Publish Language Server to npm + needs: [plan, test] + runs-on: ubuntu-latest + timeout-minutes: 10 + env: + NPM_CHANNEL: ${{ needs.plan.outputs.npm_channel }} + VERSION: ${{ needs.plan.outputs.version }} + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.plan.outputs.sha }} + - name: Install pnpm + uses: pnpm/action-setup@v4 + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'pnpm' + - name: Update npm to support Trusted Publishers + run: npm install -g npm@^11.5 + - name: Install Dependencies + run: pnpm install + - name: Set Language Server version + run: node scripts/update_package_json_files.mjs "$NPM_CHANNEL" "$VERSION" "" + - name: Build Language Server + run: pnpm --filter @prisma/language-server build + - name: Publish Language Server to npm + run: pnpm --filter @prisma/language-server publish --tag "${{ needs.plan.outputs.ls_npm_tag }}" --access public --no-git-checks + + package: + name: Build extension and create GitHub release + needs: [plan, test] + runs-on: ubuntu-latest + timeout-minutes: 10 + env: + CHANNEL: ${{ needs.plan.outputs.channel }} + NPM_CHANNEL: ${{ needs.plan.outputs.npm_channel }} + VERSION: ${{ needs.plan.outputs.version }} + TAG_NAME: ${{ needs.plan.outputs.tag_name }} + ASSET_FILE: ${{ needs.plan.outputs.asset_name }}-${{ needs.plan.outputs.version }}.vsix + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.plan.outputs.sha }} + - name: Install pnpm + uses: pnpm/action-setup@v4 + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'pnpm' + - name: Install Dependencies + run: pnpm install + - name: Set extension name and version + run: node scripts/update_package_json_files.mjs "$NPM_CHANNEL" "$VERSION" "" + - name: Replace Readme for marketplace + run: node scripts/change_readme.mjs "$CHANNEL" + - name: Build all packages + run: pnpm build + - name: Build vsix + run: cd packages/vscode && pnpm package + - name: Upload vsix as workflow artifact + uses: actions/upload-artifact@v4 + with: + name: vsix + path: packages/vscode/${{ env.ASSET_FILE }} + if-no-files-found: error + - name: Create GitHub release + env: + GH_TOKEN: ${{ github.token }} + run: | + PRERELEASE="" + if [ "$CHANNEL" = "insider" ]; then PRERELEASE="--prerelease"; fi + gh release create "$TAG_NAME" "packages/vscode/$ASSET_FILE" \ + --title "$TAG_NAME" \ + --target "${{ needs.plan.outputs.sha }}" \ + $PRERELEASE + + publish-marketplace: + name: Publish to VS Code Marketplace + needs: [plan, package] + runs-on: ubuntu-latest + timeout-minutes: 10 + env: + ASSET_FILE: ${{ needs.plan.outputs.asset_name }}-${{ needs.plan.outputs.version }}.vsix + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.plan.outputs.sha }} + - name: Install pnpm + uses: pnpm/action-setup@v4 + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'pnpm' + - name: Install Dependencies + run: pnpm install + - name: Download vsix artifact + uses: actions/download-artifact@v4 + with: + name: vsix + - name: Publish vsix to marketplace + run: cd packages/vscode && npx vsce publish --pat ${{ secrets.AZURE_DEVOPS_PERSONAL_ACCESS_TOKEN }} --packagePath "$GITHUB_WORKSPACE/$ASSET_FILE" + + publish-open-vsx: + name: Publish to open-vsx + needs: [plan, package] + runs-on: ubuntu-latest + timeout-minutes: 10 + env: + ASSET_FILE: ${{ needs.plan.outputs.asset_name }}-${{ needs.plan.outputs.version }}.vsix + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.plan.outputs.sha }} + - name: Install pnpm + uses: pnpm/action-setup@v4 + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'pnpm' + - name: Install Dependencies + run: pnpm install + - name: Download vsix artifact + uses: actions/download-artifact@v4 + with: + name: vsix + - name: Publish vsix to open-vsx.org + run: cd packages/vscode && npx ovsx --debug publish "$GITHUB_WORKSPACE/$ASSET_FILE" --pat ${{ secrets.OPEN_VSX_ACCESS_TOKEN }} diff --git a/docs/ci-cd.md b/docs/ci-cd.md index f673494e7c..78f83a59a2 100644 --- a/docs/ci-cd.md +++ b/docs/ci-cd.md @@ -1,36 +1,84 @@ # CI/CD Overview -The repository has extensive GitHub Actions workflows: - -| Workflow | Trigger | Purpose | -| ------------------------- | ------------------ | ------------------------------ | -| `1_check_for_updates.yml` | Every 5 min (cron) | Checks for new Prisma releases | -| `2_bump_versions.yml` | Triggered by #1 | Bumps Prisma dependencies | -| `3_LS_tests_publish.yml` | On version bump | Tests and publishes LS to npm | -| `4_e2e_tests.yml` | Before releases | Runs E2E tests | -| `5_build.yml` | After E2E pass | Builds extension | -| `6_publish.yml` | After build | Publishes VS Code extension | -| `PR_build_extension.yml` | On PRs | Builds `.vsix` for testing | +## Publishing pipeline + +All publishing — insider **and** stable — happens in a single workflow: +[`release.yml`](../.github/workflows/release.yml). There are no chained +workflows and no version-bump commits: the next extension version is derived +from the git release tags (`x.y.z` for stable, `insider/x.y.z` for insider, +one shared monotonic counter). The only commit a release can create is a real +dependency bump when a new Prisma CLI version is passed in. + +### Triggers + +| Trigger | Result | +| ---------------------------------------------- | ------------------------------------------------------------------------- | +| Push to `main` | Insider release | +| Manual `workflow_dispatch` | Insider or stable release, optional Prisma CLI bump, optional branch | +| `check_for_prisma_update.yml` (cron, disabled) | Dispatches `release.yml` when a new Prisma CLI version is released on npm | + +### Jobs ```mermaid graph TD - A(Cron every 5 minutes) --> B[1. Check for Prisma CLI Update] - B -->|update available?| C[2. Bump versions] - C -->D{Which NPM channel was updated?} - D -->|dev or patch-dev| E((Our release_channel is insider)) - D -->|latest| F((Our release_channel is stable)) - E --> G[3. Test Language Server and publish] - F --> G - G -->|tests and publish successful?| I[4. E2E tests VS Code Extension] - I -->|tests pass?| J[5. Build extension] - J --> K[6. Publish] - - L(Commit in the extension) --> M[1/2. Bump versions for extension only] - M -->E - N(Manual workflow dispatch) --> O[1/2. Bump and release a stable version of the extension] - O -->F + PUSH(Push to main) --> PLAN + MANUAL(Manual dispatch: channel, bump, prisma_version) --> PLAN + CRON(check_for_prisma_update.yml cron) --> PLAN + + subgraph release.yml + PLAN[plan: resolve channel + branch, derive next version from git tags,
optionally commit Prisma CLI dependency bump] + PLAN --> TEST[test: build, typecheck, LS unit tests, E2E tests
on ubuntu / macos / windows] + TEST --> LS[publish-language-server:
npm publish with dist-tag dev or latest] + TEST --> PKG[package: build vsix, upload artifact,
create GitHub release + tag] + PKG --> MKT[publish-marketplace: vsce publish] + PKG --> OVSX[publish-open-vsx: ovsx publish] + end ``` +- **plan** resolves the release channel (`insider`/`stable`), the branch, and + the next version, and outputs a single commit SHA that every later job + checks out — no state is passed through commits mid-pipeline. +- **test** gates publishing: build, typecheck, Language Server unit tests and + VS Code E2E tests on all three OSes. +- **package** builds the `.vsix` once; the same file is attached to the GitHub + release and published to both marketplaces (passed as a workflow artifact). +- Insider GitHub releases are marked as pre-releases, so the repository's + "latest release" always points to a stable version. + +### Channels and branches + +| Channel | Branch | Extension name | LS npm dist-tag | +| ------- | --------------- | ---------------- | --------------- | +| insider | `main` | `prisma-insider` | `dev` | +| stable | `stable` | `prisma` | `latest` | +| insider | `x.y.x` patches | `prisma-insider` | `dev` | + +The `stable` branch pins the Prisma CLI `latest` dependencies while `main` +tracks `dev`. When a stable release ships a new Prisma minor or major, the +`plan` job resets `stable` to `main`. Patch releases for older versions are +made by dispatching `release.yml` with an `x.y.x` branch as `ref` +(channel `insider` for a `patch-dev` CLI, `stable` for the final patch). + +### Prisma CLI update automation + +[`check_for_prisma_update.yml`](../.github/workflows/check_for_prisma_update.yml) +(cron, currently disabled — dispatch manually) compares the npm versions of +`prisma@dev`, `prisma@latest` and `prisma@patch-dev` against +`scripts/versions/prisma_*`, records new versions there, and dispatches +`release.yml` for each channel that changed. + +## Other workflows + +| Workflow | Trigger | Purpose | +| -------------------------------------- | ------------------- | -------------------------------------------- | +| `continuous-integration.yml` | PRs, push to main | Tests, lint, typecheck, Playwright | +| `PR_build_extension.yml` | PRs | Builds a `.vsix` artifact for manual testing | +| `e2e_check_for_new_published_vsix.yml` | Cron (disabled) | Detects new marketplace releases | +| `e2e_published_vsix.yml` | Dispatched by above | E2E tests against the published extension | +| `codeql-analysis.yml` | PRs, push, cron | CodeQL security analysis | +| `pr-code-security.yml` | PRs | Security checks | +| `update-api-types.yml` | Cron / manual | Updates generated API types | + ## Testing PR Builds When you open a PR, the `PR_build_extension.yml` workflow automatically builds a diff --git a/scripts/__tests__/next-extension-version.test.mjs b/scripts/__tests__/next-extension-version.test.mjs new file mode 100644 index 0000000000..9798ba5e3d --- /dev/null +++ b/scripts/__tests__/next-extension-version.test.mjs @@ -0,0 +1,84 @@ +import { describe, it, expect } from 'vitest' +import { latestReleasedVersion, releaseType, planRelease } from '../next_extension_version.mjs' + +const TAGS = [ + '31.11.0', + '31.12.0', + 'insider/31.10.5', + 'insider/31.11.1', + '@prisma/language-server@0.0.1307', + 'prisma-vscode@0.0.45', + '', +] + +describe('latestReleasedVersion', () => { + it('picks the highest version across stable and insider tags', () => { + expect(latestReleasedVersion({ tags: TAGS })).toEqual('31.12.0') + }) + + it('picks an insider tag when it is the highest', () => { + expect(latestReleasedVersion({ tags: ['31.12.0', 'insider/31.12.1'] })).toEqual('31.12.1') + }) + + it('throws when there are no release tags', () => { + expect(() => latestReleasedVersion({ tags: ['prisma-vscode@0.0.45'] })).toThrow() + }) +}) + +describe('releaseType', () => { + it('insider releases are always a patch', () => { + expect(releaseType({ channel: 'insider', bump: 'auto', prismaVersion: '7.9.0-dev.4' })).toEqual('patch') + }) + + it('stable release for a Prisma CLI patch', () => { + expect(releaseType({ channel: 'stable', bump: 'auto', prismaVersion: '7.8.1' })).toEqual('patch') + }) + + it('stable release for a Prisma CLI minor', () => { + expect(releaseType({ channel: 'stable', bump: 'auto', prismaVersion: '7.9.0' })).toEqual('minor') + }) + + it('stable release for a Prisma CLI major', () => { + expect(releaseType({ channel: 'stable', bump: 'auto', prismaVersion: '8.0.0' })).toEqual('major') + }) + + it('stable extension-only release defaults to a patch', () => { + expect(releaseType({ channel: 'stable', bump: 'auto' })).toEqual('patch') + }) + + it('an explicit bump wins over the Prisma CLI version', () => { + expect(releaseType({ channel: 'stable', bump: 'minor', prismaVersion: '7.8.1' })).toEqual('minor') + }) + + it('throws on an unknown channel', () => { + expect(() => releaseType({ channel: 'nightly' })).toThrow() + }) + + it('throws on an unknown bump', () => { + expect(() => releaseType({ channel: 'stable', bump: 'mega' })).toThrow() + }) +}) + +describe('planRelease', () => { + it('plans an insider release', () => { + expect(planRelease({ channel: 'insider', bump: 'auto', prismaVersion: '7.9.0-dev.4', tags: TAGS })).toEqual({ + version: '31.12.1', + release_type: 'patch', + tag_name: 'insider/31.12.1', + asset_name: 'prisma-insider', + ls_npm_tag: 'dev', + npm_channel: 'dev', + }) + }) + + it('plans a stable release for a Prisma CLI minor', () => { + expect(planRelease({ channel: 'stable', bump: 'auto', prismaVersion: '7.9.0', tags: TAGS })).toEqual({ + version: '31.13.0', + release_type: 'minor', + tag_name: '31.13.0', + asset_name: 'prisma', + ls_npm_tag: 'latest', + npm_channel: 'latest', + }) + }) +}) diff --git a/scripts/next_extension_version.mjs b/scripts/next_extension_version.mjs new file mode 100644 index 0000000000..daeeba42b1 --- /dev/null +++ b/scripts/next_extension_version.mjs @@ -0,0 +1,80 @@ +import semVer from 'semver' +import core from '@actions/core' +import { execSync } from 'child_process' +import { fileURLToPath } from 'url' +import { argv } from 'process' + +const BUMPS = ['auto', 'patch', 'minor', 'major'] + +// The extension version is a single monotonic counter shared by both channels. +// It is derived from the release tags (`x.y.z` for stable, `insider/x.y.z` for +// insider) instead of a committed version file, so releasing does not require +// any bot commits. +export function latestReleasedVersion({ tags }) { + const versions = tags + .map((tag) => tag.replace(/^insider\//, '')) + .filter((tag) => semVer.valid(tag)) + .sort(semVer.rcompare) + + if (versions.length === 0) { + throw new Error('Could not find any release tags (`x.y.z` or `insider/x.y.z`) to derive the next version from.') + } + return versions[0] +} + +export function releaseType({ channel, bump = 'auto', prismaVersion = '' }) { + if (channel === 'insider') { + return 'patch' + } + if (channel !== 'stable') { + throw new Error(`Unknown release channel '${channel}'. Expected 'insider' or 'stable'.`) + } + if (!BUMPS.includes(bump)) { + throw new Error(`Unknown bump '${bump}'. Expected one of: ${BUMPS.join(', ')}.`) + } + if (bump !== 'auto') { + return bump + } + // 'auto' on stable mirrors the Prisma CLI release this extension ships: + // x.0.0 -> major, x.y.0 -> minor, everything else (or no CLI bump) -> patch + if (prismaVersion !== '') { + const [, minor, patch] = prismaVersion.split('.') + if (minor === '0' && patch === '0') { + return 'major' + } + if (patch === '0') { + return 'minor' + } + } + return 'patch' +} + +export function planRelease({ channel, bump, prismaVersion, tags }) { + const currentVersion = latestReleasedVersion({ tags }) + const type = releaseType({ channel, bump, prismaVersion }) + const version = semVer.inc(currentVersion, type) + const stable = channel === 'stable' + + return { + version, + release_type: type, + tag_name: stable ? version : `insider/${version}`, + asset_name: stable ? 'prisma' : 'prisma-insider', + // npm dist-tag for @prisma/language-server + ls_npm_tag: stable ? 'latest' : 'dev', + // channel name understood by update_package_json_files.mjs + npm_channel: stable ? 'latest' : 'dev', + } +} + +// Only run top-level code if this file is being executed directly (not imported) +if (fileURLToPath(import.meta.url) === argv[1]) { + const [channel, bump = 'auto', prismaVersion = ''] = process.argv.slice(2) + const tags = execSync('git tag --list', { encoding: 'utf-8' }).split('\n') + + const plan = planRelease({ channel, bump, prismaVersion, tags }) + console.log(plan) + for (const [key, value] of Object.entries(plan)) { + core.setOutput(key, value) + } +} From 3505c33e9883687e1c2003b63e2fd1f75720613b Mon Sep 17 00:00:00 2001 From: Steven McClankerton Date: Thu, 3 Sep 2026 09:58:06 +0000 Subject: [PATCH 2/6] Pass workflow context through env vars instead of interpolating into run Semgrep (yaml.github-actions.security.run-shell-injection) flagged three steps in release.yml that interpolated `${{ github.* }}` and `${{ inputs.* }}` directly into `run:` scripts, where a crafted dispatch input could inject shell code into the runner. Every value a run script needs is now bound to an env var on the step or job and referenced as "$VAR". The same treatment is applied to the npm versions dispatched by check_for_prisma_update.yml, which come from the registry, and to the two marketplace tokens: vsce and ovsx read VSCE_PAT and OVSX_PAT from the environment, so the tokens no longer appear on a command line. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01YJFVnTXe5iAtFHpARAg4ZU --- .github/workflows/check_for_prisma_update.yml | 8 +++- .github/workflows/release.yml | 42 +++++++++++++------ 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/.github/workflows/check_for_prisma_update.yml b/.github/workflows/check_for_prisma_update.yml index 05e4935468..66a94e620d 100644 --- a/.github/workflows/check_for_prisma_update.yml +++ b/.github/workflows/check_for_prisma_update.yml @@ -61,10 +61,14 @@ jobs: - name: Release insider (Prisma dev) if: steps.check_update.outputs.dev_version - run: gh workflow run release.yml --ref main -f channel=insider -f prisma_version="${{ steps.check_update.outputs.dev_version }}" + env: + DEV_VERSION: ${{ steps.check_update.outputs.dev_version }} + run: gh workflow run release.yml --ref main -f channel=insider -f prisma_version="$DEV_VERSION" - name: Release stable (Prisma latest) if: steps.check_update.outputs.latest_version - run: gh workflow run release.yml --ref main -f channel=stable -f prisma_version="${{ steps.check_update.outputs.latest_version }}" + env: + LATEST_VERSION: ${{ steps.check_update.outputs.latest_version }} + run: gh workflow run release.yml --ref main -f channel=stable -f prisma_version="$LATEST_VERSION" - name: Release insider from patch branch (Prisma patch-dev) if: steps.check_update.outputs.patch-dev_version env: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a552fbc878..aa80317baf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -79,13 +79,18 @@ jobs: steps: - name: Resolve channel and branch id: params + env: + EVENT_NAME: ${{ github.event_name }} + PUSH_SHA: ${{ github.sha }} + INPUT_CHANNEL: ${{ inputs.channel }} + INPUT_REF: ${{ inputs.ref }} run: | - if [ "${{ github.event_name }}" = "push" ]; then + if [ "$EVENT_NAME" = "push" ]; then CHANNEL=insider - REF="${{ github.sha }}" + REF="$PUSH_SHA" else - CHANNEL="${{ inputs.channel }}" - REF="${{ inputs.ref }}" + CHANNEL="$INPUT_CHANNEL" + REF="$INPUT_REF" if [ -z "$REF" ]; then if [ "$CHANNEL" = "stable" ]; then REF=stable; else REF=main; fi fi @@ -110,7 +115,11 @@ jobs: run: pnpm install - name: Compute next extension version id: version - run: node scripts/next_extension_version.mjs "${{ steps.params.outputs.channel }}" "${{ inputs.bump || 'auto' }}" "${{ inputs.prisma_version }}" + env: + CHANNEL: ${{ steps.params.outputs.channel }} + BUMP: ${{ inputs.bump || 'auto' }} + PRISMA_VERSION: ${{ inputs.prisma_version }} + run: node scripts/next_extension_version.mjs "$CHANNEL" "$BUMP" "$PRISMA_VERSION" - name: Reset stable branch to main (new Prisma minor/major) if: steps.params.outputs.channel == 'stable' && inputs.prisma_version != '' && steps.version.outputs.release_type != 'patch' run: | @@ -118,11 +127,16 @@ jobs: git push --force origin stable - name: Bump Prisma dependencies if: inputs.prisma_version != '' + env: + NPM_CHANNEL: ${{ steps.version.outputs.npm_channel }} + VERSION: ${{ steps.version.outputs.version }} + PRISMA_VERSION: ${{ inputs.prisma_version }} + RELEASE_REF: ${{ steps.params.outputs.ref }} run: | - node scripts/update_package_json_files.mjs "${{ steps.version.outputs.npm_channel }}" "${{ steps.version.outputs.version }}" "${{ inputs.prisma_version }}" + node scripts/update_package_json_files.mjs "$NPM_CHANNEL" "$VERSION" "$PRISMA_VERSION" sh scripts/set_git_credentials.sh - git commit -am "[skip ci] bump Prisma CLI to ${{ inputs.prisma_version }} and extension to ${{ steps.version.outputs.version }}" - git push origin "HEAD:${{ steps.params.outputs.ref }}" + git commit -am "[skip ci] bump Prisma CLI to $PRISMA_VERSION and extension to $VERSION" + git push origin "HEAD:$RELEASE_REF" - name: Resolve release commit id: sha run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" @@ -167,6 +181,7 @@ jobs: env: NPM_CHANNEL: ${{ needs.plan.outputs.npm_channel }} VERSION: ${{ needs.plan.outputs.version }} + LS_NPM_TAG: ${{ needs.plan.outputs.ls_npm_tag }} steps: - uses: actions/checkout@v4 with: @@ -187,7 +202,7 @@ jobs: - name: Build Language Server run: pnpm --filter @prisma/language-server build - name: Publish Language Server to npm - run: pnpm --filter @prisma/language-server publish --tag "${{ needs.plan.outputs.ls_npm_tag }}" --access public --no-git-checks + run: pnpm --filter @prisma/language-server publish --tag "$LS_NPM_TAG" --access public --no-git-checks package: name: Build extension and create GitHub release @@ -200,6 +215,7 @@ jobs: VERSION: ${{ needs.plan.outputs.version }} TAG_NAME: ${{ needs.plan.outputs.tag_name }} ASSET_FILE: ${{ needs.plan.outputs.asset_name }}-${{ needs.plan.outputs.version }}.vsix + SHA: ${{ needs.plan.outputs.sha }} steps: - uses: actions/checkout@v4 with: @@ -235,7 +251,7 @@ jobs: if [ "$CHANNEL" = "insider" ]; then PRERELEASE="--prerelease"; fi gh release create "$TAG_NAME" "packages/vscode/$ASSET_FILE" \ --title "$TAG_NAME" \ - --target "${{ needs.plan.outputs.sha }}" \ + --target "$SHA" \ $PRERELEASE publish-marketplace: @@ -245,6 +261,7 @@ jobs: timeout-minutes: 10 env: ASSET_FILE: ${{ needs.plan.outputs.asset_name }}-${{ needs.plan.outputs.version }}.vsix + VSCE_PAT: ${{ secrets.AZURE_DEVOPS_PERSONAL_ACCESS_TOKEN }} steps: - uses: actions/checkout@v4 with: @@ -263,7 +280,7 @@ jobs: with: name: vsix - name: Publish vsix to marketplace - run: cd packages/vscode && npx vsce publish --pat ${{ secrets.AZURE_DEVOPS_PERSONAL_ACCESS_TOKEN }} --packagePath "$GITHUB_WORKSPACE/$ASSET_FILE" + run: cd packages/vscode && npx vsce publish --packagePath "$GITHUB_WORKSPACE/$ASSET_FILE" publish-open-vsx: name: Publish to open-vsx @@ -272,6 +289,7 @@ jobs: timeout-minutes: 10 env: ASSET_FILE: ${{ needs.plan.outputs.asset_name }}-${{ needs.plan.outputs.version }}.vsix + OVSX_PAT: ${{ secrets.OPEN_VSX_ACCESS_TOKEN }} steps: - uses: actions/checkout@v4 with: @@ -290,4 +308,4 @@ jobs: with: name: vsix - name: Publish vsix to open-vsx.org - run: cd packages/vscode && npx ovsx --debug publish "$GITHUB_WORKSPACE/$ASSET_FILE" --pat ${{ secrets.OPEN_VSX_ACCESS_TOKEN }} + run: cd packages/vscode && npx ovsx --debug publish "$GITHUB_WORKSPACE/$ASSET_FILE" From 52ba66f6c82521a06ac61e0ade0f5178ee08435d Mon Sep 17 00:00:00 2001 From: Steven McClankerton Date: Thu, 3 Sep 2026 09:59:01 +0000 Subject: [PATCH 3/6] Remove the release scripts the old workflow chain left behind Nothing references these any more now that release.yml derives the version from the git tags: - bump_extension_version.mjs and versions/extension_latest, replaced by next_extension_version.mjs - is_minor_or_major_release.mjs, get_tag_name.mjs, commit_and_push.sh and setup_branch.sh, which only the deleted numbered workflows called - util.mjs writeToVersionFile, whose only caller was bump_extension_version.mjs The __tests__ directory also carried Jest-era .js copies of every .mjs test. The vitest config matches **/__tests__/**/*.test.mjs, so those copies never ran; they are removed along with the extension-version tests covering the deleted bump script. README build badges, docs/architecture.md and packages/vscode/CONTRIBUTING.md pointed at workflow files that no longer exist and now point at release.yml, check_for_prisma_update.yml and continuous-integration.yml. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01YJFVnTXe5iAtFHpARAg4ZU --- README.md | 12 +- docs/architecture.md | 7 +- packages/vscode/CONTRIBUTING.md | 12 +- .../__snapshots__/change-readme.test.js.snap | 207 ------------------ scripts/__tests__/change-readme.test.js | 23 -- scripts/__tests__/dependencies.test.js | 21 -- scripts/__tests__/extension-version.test.js | 93 -------- scripts/__tests__/extension-version.test.mjs | 93 -------- scripts/bump_extension_version.mjs | 77 ------- scripts/commit_and_push.sh | 33 --- scripts/get_tag_name.mjs | 14 -- scripts/is_minor_or_major_release.mjs | 12 - scripts/setup_branch.sh | 60 ----- scripts/util.mjs | 4 - scripts/versions/extension_latest | 1 - 15 files changed, 16 insertions(+), 653 deletions(-) delete mode 100644 scripts/__tests__/__snapshots__/change-readme.test.js.snap delete mode 100644 scripts/__tests__/change-readme.test.js delete mode 100644 scripts/__tests__/dependencies.test.js delete mode 100644 scripts/__tests__/extension-version.test.js delete mode 100644 scripts/__tests__/extension-version.test.mjs delete mode 100644 scripts/bump_extension_version.mjs delete mode 100644 scripts/commit_and_push.sh delete mode 100644 scripts/get_tag_name.mjs delete mode 100644 scripts/is_minor_or_major_release.mjs delete mode 100644 scripts/setup_branch.sh delete mode 100644 scripts/versions/extension_latest diff --git a/README.md b/README.md index e1b9564c7a..bc124ffda0 100644 --- a/README.md +++ b/README.md @@ -56,10 +56,10 @@ Press `F5` in VS Code to launch the extension in debug mode. ## Build Status -[![E2E tests before Insider release][e2e-insider-badge]][e2e-insider-action] -[![Language Server tests][ls-tests-badge]][ls-tests-action] +[![Release][release-badge]][release-action] +[![CI][ci-badge]][ci-action] -[e2e-insider-badge]: https://github.com/prisma/language-tools/workflows/5.%20Integration%20tests%20in%20VSCode%20folder%20with%20published%20LS/badge.svg?branch=main -[e2e-insider-action]: https://github.com/prisma/language-tools/actions/workflows/5_e2e_tests.yml?query=branch%3Amain -[ls-tests-badge]: https://github.com/prisma/language-tools/workflows/3.%20Unit%20tests%20for%20LS%20and%20publish/badge.svg?branch=main -[ls-tests-action]: https://github.com/prisma/language-tools/actions/workflows/3_LS_tests_publish.yml?query=branch%3Amain +[release-badge]: https://github.com/prisma/language-tools/actions/workflows/release.yml/badge.svg?branch=main +[release-action]: https://github.com/prisma/language-tools/actions/workflows/release.yml?query=branch%3Amain +[ci-badge]: https://github.com/prisma/language-tools/actions/workflows/continuous-integration.yml/badge.svg?branch=main +[ci-action]: https://github.com/prisma/language-tools/actions/workflows/continuous-integration.yml?query=branch%3Amain diff --git a/docs/architecture.md b/docs/architecture.md index 55822ad18d..a0dd696b33 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -69,9 +69,10 @@ assets (`pglite.data`, `pglite.wasm`) are copied separately ⁴ Kept separate to support runtime switching between Prisma 6 and latest language servers via the `prisma.pinToPrisma6` setting -> **Note:** These dependencies are automatically updated via CI. A cron job -> runs every 5 minutes checking for new Prisma releases -> (see `.github/workflows/1_check_for_updates.yml`). +> **Note:** These dependencies are updated via CI by +> `.github/workflows/check_for_prisma_update.yml`, which checks npm for new +> Prisma CLI releases and dispatches `release.yml`. Its cron schedule is +> currently disabled; dispatch it manually. See [CI/CD](ci-cd.md). ## File Organization diff --git a/packages/vscode/CONTRIBUTING.md b/packages/vscode/CONTRIBUTING.md index 3a376e2dfd..59289474d1 100644 --- a/packages/vscode/CONTRIBUTING.md +++ b/packages/vscode/CONTRIBUTING.md @@ -94,16 +94,16 @@ For releases that don't coincide with a Prisma ORM release: **Insider release:** -- Push to `main` or a patch branch (e.g., `35.0.x`) -- Automatically triggers [`1/2. Bump versions for extension only`][bump-workflow] +- Push to `main`, which triggers [`Release`][release-workflow] +- For a patch branch (e.g. `35.0.x`), dispatch [`Release`][release-workflow] + with channel `insider` and that branch as `ref` **Stable release:** -- Manually trigger [`1/2. Bump and release a stable version`][stable-workflow] -- Select release type: `patch`, `minor`, or `major` +- Manually dispatch [`Release`][release-workflow] with channel `stable` +- Select the bump: `auto`, `patch`, `minor` or `major` -[bump-workflow]: ../../.github/workflows/1_2_bump_extension_only.yml -[stable-workflow]: ../../.github/workflows/1_2_stable_extension_release.yml +[release-workflow]: ../../.github/workflows/release.yml ## Dependencies diff --git a/scripts/__tests__/__snapshots__/change-readme.test.js.snap b/scripts/__tests__/__snapshots__/change-readme.test.js.snap deleted file mode 100644 index a711740514..0000000000 --- a/scripts/__tests__/__snapshots__/change-readme.test.js.snap +++ /dev/null @@ -1,207 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`changeReadme it should with an insider release 1`] = ` -"

Prisma VS Code Extension - Insider

- -## Insider Build - -This is the Insider Build of the [Prisma VS Code extension](https://marketplace.visualstudio.com/items?itemName=Prisma.prisma). Most users should not use this version, but instead install -the regular version of the extension.This version of the extension may contain unstable prerelease code and is directly built from the main branch. - -**Only use this extension if you are also using the dev version of the CLI.** - -Please note that you should not have the regular and insider version of the extension installed at the same time. - -## Preview - -
- Syntax-Highlighting - -Syntax highlighting eases visual comprehension of the Prisma schema. -![Preview Schema](https://user-images.githubusercontent.com/1328733/147264843-fc32c2aa-7490-4e49-9478-abc16cbd0682.png) - -
-
- Formatting - -Formatting ensures consistent indentation of your models for better readability. -![Formatting](https://user-images.githubusercontent.com/1328733/147264852-849cb539-9bdc-4916-9d0f-483536061f7c.gif) - -
-
- Linting and autocompletion - -Linting shows inline errors in the schema, and autocompletion assists in defining the correct type. -![Linting and autocompletion](https://user-images.githubusercontent.com/1328733/147265321-2e1956ec-9f57-4ff3-9493-8163a727308d.gif) - -
-
- Contextual suggestions - -Contextual suggestions assist in defining field types, models, and relations while formatting automatically defines back relations. -![Contextual suggestions](https://user-images.githubusercontent.com/1328733/147265323-4eb397b4-acda-4c78-9f27-1230d7ea4603.gif) - -
-
- Jump-to-definition - -Easily navigate definitions, i.e. models in the Prisma schema. - -![Jump-to-definition](https://user-images.githubusercontent.com/1328733/147265315-838cd63c-e0c6-485c-aec9-1b1707291719.gif) - -
- -## Build information - -- This version is built from commit [undefined](https://github.com/prisma/language-tools/commit/undefined). -- This is for Prisma CLI 2.5.0-dev.4. - -## Contributing - -Read more about [how to contribute to the Prisma VS Code extension](./packages/vscode/CONTRIBUTING.md) - -## Telemetry - -This extension collects telemetry data to help us better the usage of the extension. You can read more about that [here](https://www.prisma.io/docs/reference/more/telemetry). -The extension respects: - -- the \`telemetry.enableTelemetry\` setting in VS Code ([deprecated since v1.61](https://code.visualstudio.com/updates/v1_61#_telemetry-settings)). -- the \`telemetry.telemetryLevel\` setting in VS Code (see [docs](https://code.visualstudio.com/docs/getstarted/telemetry)) - -If you want to opt out of telemetry you can either, in your VS Code settings: - -- set \`"telemetry.enableTelemetry": false\` -- set \`"telemetry.telemetryLevel": "off"\` (or "crash" or "error") - -## Security - -If you have a security issue to report, please contact us at [security@prisma.io](mailto:security@prisma.io?subject=[GitHub]%20Prisma%202%20Security%20Report%20VSCode) -" -`; - -exports[`changeReadme it should work with a stable release 1`] = ` -"

Prisma VS Code Extension

-
- Adds a database management UI, syntax highlighting, linting, code completion, formatting, jump-to-definition and more. -
- -## Features - -### Visual database management UI - -The Prisma VS Code extension features a database managent UI that you can access via the **Prisma logo** in the sidebar. It allows to manage Prisma Postgres instances: - -- Authenticate with the [Prisma Console](https://console.prisma.io) -- Create and delete Prisma Postgres instances (remote & local) -- View and edit data via an embedded Prisma Studio -- Visualize your database schema - -### Editor support - -- Syntax highlighting of \`schema.prisma\` -- Linting - - Diagnostic tools are used to surface errors and warnings in your schema file as you type. -- Code Completion - - Completion results appear for symbols as you type. - - You can trigger this manually with the \`Ctrl+Space\` shortcut. -- Documentation help - - Documentation of a completion result pops up as completion results are provided. -- Quick info on hover - - Documentation Comments (\`///\`) of models and enums appear anywhere you hover over their usages. -- Go to Definition - - Jump to or peek a model or enum's declaration. -- Formatting - - Format code either manually or on save (if configured). - - _To automatically format on save, add the following to your \`settings.json\` file:_ - \`\`\` - "editor.formatOnSave": true - \`\`\` - - _To enable formatting in combination with \`prettier\`, add the following to your \`settings.json\` file:_ - \`\`\` - "[prisma]": { - "editor.defaultFormatter": "Prisma.prisma" - }, - \`\`\` - or use the [Prettier plugin for Prisma](https://github.com/umidbekk/prettier-plugin-prisma) -- Rename - - Rename models, enums, fields and enum values - - Click into the model or enum, press \`F2\` and then type the new desired name and press \`Enter\` - - All usages will be renamed - - Automatically applies \`@map\` or \`@@map\` on the schema -- Quick-fixes - - Quickly fix typos in model and enum names - - Create new models and enums with a single click - -## Preview - -
- Database management UI - -The database management UI gives you a visual way to perform various database workflows. -![Preview Schema](https://cdn.sanity.io/images/p2zxqf70/production/f7a4f862f7f12d96c98eafef1b6bf0f2d0cac943-3740x1964.png) - -
- -
- Syntax highlighting - -Syntax highlighting eases visual comprehension of the Prisma schema. -![Preview Schema](https://user-images.githubusercontent.com/1328733/147264843-fc32c2aa-7490-4e49-9478-abc16cbd0682.png) - -
-
- Formatting - -Formatting ensures consistent indentation of your models for better readability. -![Formatting](https://user-images.githubusercontent.com/1328733/147264852-849cb539-9bdc-4916-9d0f-483536061f7c.gif) - -
-
- Linting and autocompletion - -Linting shows inline errors in the schema, and autocompletion assists in defining the correct type. -![Linting and autocompletion](https://user-images.githubusercontent.com/1328733/147265321-2e1956ec-9f57-4ff3-9493-8163a727308d.gif) - -
-
- Contextual suggestions - -Contextual suggestions assist in defining field types, models, and relations while formatting automatically defines back relations. -![Contextual suggestions](https://user-images.githubusercontent.com/1328733/147265323-4eb397b4-acda-4c78-9f27-1230d7ea4603.gif) - -
-
- Jump-to-definition - -Easily navigate definitions, i.e. models in the Prisma schema. - -![Jump-to-definition](https://user-images.githubusercontent.com/1328733/147265315-838cd63c-e0c6-485c-aec9-1b1707291719.gif) - -
- -## Contributing - -Read more about [how to contribute to the Prisma VS Code extension](./packages/vscode/CONTRIBUTING.md) - -## Telemetry - -This extension collects telemetry data to help us better the usage of the extension. You can read more about that [in Prisma's documentation](https://www.prisma.io/docs/reference/more/telemetry). -The extension respects: - -- the \`telemetry.enableTelemetry\` setting in VS Code ([deprecated since v1.61](https://code.visualstudio.com/updates/v1_61#_telemetry-settings)). -- the \`telemetry.telemetryLevel\` setting in VS Code (see [docs](https://code.visualstudio.com/docs/getstarted/telemetry)) - -If you want to opt out of telemetry you can either, in your VS Code settings: - -- set \`"telemetry.enableTelemetry": false\` -- set \`"telemetry.telemetryLevel": "off"\` (or "crash" or "error") - -## Build information - -This is for Prisma CLI 2.4.0. - -## Security - -If you have a security issue to report, please contact us at [security@prisma.io](mailto:security@prisma.io?subject=[GitHub]%20Prisma%202%20Security%20Report%20VSCode) -" -`; diff --git a/scripts/__tests__/change-readme.test.js b/scripts/__tests__/change-readme.test.js deleted file mode 100644 index f406e78301..0000000000 --- a/scripts/__tests__/change-readme.test.js +++ /dev/null @@ -1,23 +0,0 @@ -import { describe, it, expect } from 'vitest' -import { getNewReadMeContent } from '../change_readme.mjs' - -describe('changeReadme', () => { - - it('it should with an insider release', () => { - expect( - getNewReadMeContent({ - releaseChannel: 'insider', - cliVersion: '2.5.0-dev.4', - }), - ).toMatchSnapshot() - }) - - it('it should work with a stable release', () => { - expect( - getNewReadMeContent({ - releaseChannel: 'stable', - cliVersion: '2.4.0', - }), - ).toMatchSnapshot() - }) -}) diff --git a/scripts/__tests__/dependencies.test.js b/scripts/__tests__/dependencies.test.js deleted file mode 100644 index d9c08375a8..0000000000 --- a/scripts/__tests__/dependencies.test.js +++ /dev/null @@ -1,21 +0,0 @@ -import { describe, it, expect } from 'vitest' -import semver from 'semver' -import { createRequire } from 'module' -import { fileURLToPath } from 'url' -import { dirname } from 'path' - -const __filename = fileURLToPath(import.meta.url) -const __dirname = dirname(__filename) -const require = createRequire(import.meta.url) - -describe('version incompatibilities', () => { - it('@types/vscode should be less than or equal to engine.vscode version', () => { - const packageJson = require('../../packages/vscode/package.json') - const vscodeTypes = packageJson.devDependencies['@types/vscode'] - const vscodeEngine = packageJson.engines.vscode - expect( - semver.ltr(vscodeTypes, vscodeEngine) || - semver.eq(vscodeTypes, vscodeEngine.substring(1)), - ).toBeTruthy() - }) -}) diff --git a/scripts/__tests__/extension-version.test.js b/scripts/__tests__/extension-version.test.js deleted file mode 100644 index 0d2337e1c2..0000000000 --- a/scripts/__tests__/extension-version.test.js +++ /dev/null @@ -1,93 +0,0 @@ -import { describe, it, expect, beforeAll } from 'vitest' -import { nextVersion } from '../bump_extension_version.mjs' - -describe('next extension version', () => { - // - // normal insider release - // - it('insider release triggered by Prisma CLI', () => { - expect( - nextVersion({ - currentVersion: '35.0.1', - trigger: 'prisma-dev-release', - prismaLatest: '2.4.0', - }), - ).toEqual('35.0.2') - }) - - it('insider release triggered by Prisma CLI after an extension only release', () => { - expect( - nextVersion({ - currentVersion: '35.0.2', - trigger: 'prisma-dev-release', - prismaLatest: '2.4.0', - }), - ).toEqual('35.0.3') - }) - - it('a first Prisma CLI patch on stable', () => { - expect( - nextVersion({ - currentVersion: '32.5.0', - trigger: 'prisma-latest-release', - prismaLatest: '2.5.1', - }), - ).toEqual('32.5.1') - }) - - it('a Prisma CLI patch on stable', () => { - expect( - nextVersion({ - currentVersion: '32.5.1', - trigger: 'prisma-latest-release', - prismaLatest: '2.5.1', - }), - ).toEqual('32.5.2') - }) - - // - // extension version bumps - // - it('patch version bump', () => { - expect( - nextVersion({ - currentVersion: '35.1.3', - trigger: 'extension-patch-release', - prismaLatest: '2.5.0', - }), - ).toEqual('35.1.4') - }) - - it('minor version bump', () => { - expect( - nextVersion({ - currentVersion: '35.1.3', - trigger: 'extension-minor-release', - prismaLatest: '2.5.0', - }), - ).toEqual('35.2.0') - }) - - it('major version bump', () => { - expect( - nextVersion({ - currentVersion: '35.1.3', - trigger: 'extension-major-release', - prismaLatest: '2.5.0', - }), - ).toEqual('36.0.0') - }) - - // - // first extension release after Prisma CLI major update - // - it('first extension release after a major Prisma CLI update (4.0.0)', () => { - expect( - nextVersion({ - currentVersion: '32.15.9', - trigger: 'prisma-latest-release', - prismaLatest: '4.0.0', - }), - ).toEqual('33.0.0') - }) -}) diff --git a/scripts/__tests__/extension-version.test.mjs b/scripts/__tests__/extension-version.test.mjs deleted file mode 100644 index d46635390b..0000000000 --- a/scripts/__tests__/extension-version.test.mjs +++ /dev/null @@ -1,93 +0,0 @@ -import { describe, it, expect } from 'vitest' -import { nextVersion } from '../bump_extension_version.mjs' - -describe('next extension version', () => { - // - // normal insider release - // - it('insider release triggered by Prisma CLI', () => { - expect( - nextVersion({ - currentVersion: '35.0.1', - trigger: 'prisma-dev-release', - prismaLatest: '2.4.0', - }), - ).toEqual('35.0.2') - }) - - it('insider release triggered by Prisma CLI after an extension only release', () => { - expect( - nextVersion({ - currentVersion: '35.0.2', - trigger: 'prisma-dev-release', - prismaLatest: '2.4.0', - }), - ).toEqual('35.0.3') - }) - - it('a first Prisma CLI patch on stable', () => { - expect( - nextVersion({ - currentVersion: '32.5.0', - trigger: 'prisma-latest-release', - prismaLatest: '2.5.1', - }), - ).toEqual('32.5.1') - }) - - it('a Prisma CLI patch on stable', () => { - expect( - nextVersion({ - currentVersion: '32.5.1', - trigger: 'prisma-latest-release', - prismaLatest: '2.5.1', - }), - ).toEqual('32.5.2') - }) - - // - // extension version bumps - // - it('patch version bump', () => { - expect( - nextVersion({ - currentVersion: '35.1.3', - trigger: 'extension-patch-release', - prismaLatest: '2.5.0', - }), - ).toEqual('35.1.4') - }) - - it('minor version bump', () => { - expect( - nextVersion({ - currentVersion: '35.1.3', - trigger: 'extension-minor-release', - prismaLatest: '2.5.0', - }), - ).toEqual('35.2.0') - }) - - it('major version bump', () => { - expect( - nextVersion({ - currentVersion: '35.1.3', - trigger: 'extension-major-release', - prismaLatest: '2.5.0', - }), - ).toEqual('36.0.0') - }) - - // - // first extension release after Prisma CLI major update - // - it('first extension release after a major Prisma CLI update (4.0.0)', () => { - expect( - nextVersion({ - currentVersion: '32.15.9', - trigger: 'prisma-latest-release', - prismaLatest: '4.0.0', - }), - ).toEqual('33.0.0') - }) -}) diff --git a/scripts/bump_extension_version.mjs b/scripts/bump_extension_version.mjs deleted file mode 100644 index fcccc0e154..0000000000 --- a/scripts/bump_extension_version.mjs +++ /dev/null @@ -1,77 +0,0 @@ -import semVer from 'semver' -import core from '@actions/core' -import { fileURLToPath } from 'url' -import { argv } from 'process' -import { readVersionFile, writeToVersionFile } from './util.mjs' - -export function nextVersion({ currentVersion, trigger, prismaLatest }) { - switch (trigger) { - case 'extension-patch-release': - case 'prisma-dev-release': - case 'prisma-patch-dev-release': - return semVer.inc(currentVersion, 'patch') - case 'extension-minor-release': - return semVer.inc(currentVersion, 'minor') - case 'extension-major-release': - return semVer.inc(currentVersion, 'major') - case 'prisma-latest-release': - // Stable release trigger bumps according to Prisma CLI version change - if (isMajorRelease(prismaLatest)) { - return semVer.inc(currentVersion, 'major') - } else if (isMinorRelease(prismaLatest)) { - return semVer.inc(currentVersion, 'minor') - } else { - return semVer.inc(currentVersion, 'patch') - } - default: - throw new Error( - 'This function needs to be called with a known trigger: extension-(patch|minor|major)-release or prisma-(latest|dev|patch-dev)-release.', - ) - } -} - -export function currentExtensionVersion() { - return readVersionFile({ fileName: 'extension_latest' }) -} - -export function bumpExtensionVersionInScriptFiles({ nextVersion: nextVersionValue }) { - writeToVersionFile({ fileName: 'extension_latest', content: nextVersionValue }) -} - -function isMinorRelease(prismaVersion) { - const [, minor, patch] = prismaVersion.split('.') - return minor !== '0' && patch === '0' -} - -function isMajorRelease(prismaVersion) { - const [, minor, patch] = prismaVersion.split('.') - return minor === '0' && patch === '0' -} - -export function isMinorOrMajorRelease(prismaVersion) { - return isMinorRelease(prismaVersion) || isMajorRelease(prismaVersion) -} - -// Only run top-level code if this file is being executed directly (not imported) -if (fileURLToPath(import.meta.url) === argv[1]) { - const args = process.argv.slice(2) - const trigger = args[0] - - // Get the current extension version - const currentVersionOfExtension = currentExtensionVersion() - console.log(`Current extension version: ${currentVersionOfExtension}`) - - // "Calculate" next version number - const version = nextVersion({ - currentVersion: currentVersionOfExtension, - trigger, - prismaLatest: readVersionFile({ fileName: 'prisma_latest' }), - }) - console.log(`Next extension version ${version}.`) - core.setOutput('next_extension_version', version) - - // Bump in file - bumpExtensionVersionInScriptFiles({ nextVersion: version }) - console.log(`Bumped extension version in scripts/version folder.`) -} - diff --git a/scripts/commit_and_push.sh b/scripts/commit_and_push.sh deleted file mode 100644 index edcd8a7705..0000000000 --- a/scripts/commit_and_push.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/sh -# shellcheck disable=SC2086 - -set -eu - -# For local development, in production, the environment will be set though GH actions and GH secrets -if [ -f ".envrc" ]; then - echo "Loading .envrc" - # shellcheck disable=SC1091 - . .envrc -else - echo "No .envrc" -fi - -COMMIT_MESSAGE=$1 -echo "COMMIT_MESSAGE: $COMMIT_MESSAGE" - -BRANCH=${2-main} -echo "BRANCH: $BRANCH" - -NEW_BRANCH=${3-false} -echo "NEW BRANCH: $NEW_BRANCH" - -git add -A . -git commit -am "$COMMIT_MESSAGE" - -if [ "$NEW_BRANCH" = "false" ]; then - git pull --rebase - git push -else - ## Do not rebase on newly created branch - git push --set-upstream origin $BRANCH -fi diff --git a/scripts/get_tag_name.mjs b/scripts/get_tag_name.mjs deleted file mode 100644 index c2bcc0d175..0000000000 --- a/scripts/get_tag_name.mjs +++ /dev/null @@ -1,14 +0,0 @@ -import core from '@actions/core' - -const args = process.argv.slice(2) -const releaseChannel = args[0] -const vscodeVersion = args[1] - -if (releaseChannel === 'stable') { - core.setOutput('tag_name', vscodeVersion) - core.setOutput('asset_name', 'prisma') -} else { - core.setOutput('tag_name', `insider/${vscodeVersion}`) - core.setOutput('asset_name', 'prisma-insider') -} - diff --git a/scripts/is_minor_or_major_release.mjs b/scripts/is_minor_or_major_release.mjs deleted file mode 100644 index 5b2650c886..0000000000 --- a/scripts/is_minor_or_major_release.mjs +++ /dev/null @@ -1,12 +0,0 @@ -import core from '@actions/core' -import { isMinorOrMajorRelease } from './bump_extension_version.mjs' -import { readVersionFile } from './util.mjs' - -const args = process.argv.slice(2) -const releaseChannel = args[0] -if (releaseChannel === 'latest') { - const prisma_latest = readVersionFile({ fileName: 'prisma_latest' }) - const isMinorOrMajor = isMinorOrMajorRelease(prisma_latest) - core.setOutput('is_minor_or_major_release', isMinorOrMajor) -} - diff --git a/scripts/setup_branch.sh b/scripts/setup_branch.sh deleted file mode 100644 index 04b0ca34cf..0000000000 --- a/scripts/setup_branch.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/sh - -set -eu - -# For local development, in production, the environment will be set though GH actions and GH secrets -if [ -f ".envrc" ]; then - echo "Loading .envrc" - # shellcheck disable=SC1091 - . .envrc -else - echo "No .envrc" -fi - -NPM_CHANNEL=$1 - -if [ "$NPM_CHANNEL" = "dev" ]; then - echo "Not switching branch because we are on NPM_CHANNEL dev." - echo "branch=main" >> "$GITHUB_OUTPUT" -elif [ "$NPM_CHANNEL" = "promote_patch-dev" ]; then - PATCH_BRANCH=$(node scripts/setup_branch.mjs "patch-dev") - git checkout -f stable - git reset --hard "$PATCH_BRANCH" # Reset stable to patch-dev branch - git push -f # do not merge, only use state of PATCH_BRANCH - echo "branch=main" >> "$GITHUB_OUTPUT" -else - BRANCH=$(node scripts/setup_branch.mjs "$NPM_CHANNEL") - echo "BRANCH: $BRANCH" - - git fetch - - EXISTS_ALREADY=$(git ls-remote --heads origin "$BRANCH") - echo "$EXISTS_ALREADY" - - if [ "${EXISTS_ALREADY}" = "" ]; then - echo "Branch $BRANCH does not exist yet." - echo "new_branch=true" >> "$GITHUB_OUTPUT" - echo "branch=$BRANCH" >> "$GITHUB_OUTPUT" - - if [ "$ENVIRONMENT" = "PRODUCTION" ]; then - git config --global user.email "prismabots@gmail.com" - git config --global user.name "Prismo" - - if [ "$NPM_CHANNEL" = "latest" ]; then - git checkout -f -b "$BRANCH" - else - # Patch branch - NPM_VERSION=$(cat scripts/versions/tested_extension_stable) - echo "NPM_VERSION to base new branch on: $NPM_VERSION" - git checkout -f -b "$BRANCH" "$NPM_VERSION" - fi - - else - echo "Not setting up repo because ENVIRONMENT is not set" - fi - else - git checkout -f "$BRANCH" - echo "$BRANCH exists already." - echo "branch=$BRANCH" >> "$GITHUB_OUTPUT" - fi -fi diff --git a/scripts/util.mjs b/scripts/util.mjs index 01fb01efb3..e2c5176880 100644 --- a/scripts/util.mjs +++ b/scripts/util.mjs @@ -5,10 +5,6 @@ import { fileURLToPath } from 'url' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) -export function writeToVersionFile({ fileName, content }) { - fs.writeFileSync(path.join(__dirname, 'versions', `./${fileName}`), content) -} - export function readVersionFile({ fileName = '' }) { return fs .readFileSync(path.join(__dirname, 'versions', `./${fileName}`), { diff --git a/scripts/versions/extension_latest b/scripts/versions/extension_latest deleted file mode 100644 index 77424f0941..0000000000 --- a/scripts/versions/extension_latest +++ /dev/null @@ -1 +0,0 @@ -31.12.2 \ No newline at end of file From 31f603c5b6ecab5e51ac915bc2d791a6d1979988 Mon Sep 17 00:00:00 2001 From: Steven McClankerton Date: Thu, 3 Sep 2026 10:08:08 +0000 Subject: [PATCH 4/6] Scope release permissions per job and validate the Prisma CLI version Two review findings. The workflow granted every job contents: write and id-token: write, so the test and dependency-install steps ran with release-write capability and could request OIDC tokens. The workflow default is now contents: read; plan gets contents: write for the dependency-bump commit and the stable branch reset, package gets contents: write to create the tag and GitHub release, and publish-language-server gets id-token: write for npm Trusted Publishers. test and both marketplace jobs inherit read. releaseType split the Prisma CLI version as a raw string, so 'invalid.0.0' derived a major bump and a prerelease silently fell through to a patch. It now parses the version with semVer.parse and rejects both invalid and prerelease versions on the stable channel, whatever the bump. The check runs in the plan job before the stable branch is reset, so a bad input fails the release rather than leaving stable force-pushed. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01YJFVnTXe5iAtFHpARAg4ZU --- .github/workflows/release.yml | 10 +++++++-- .../__tests__/next-extension-version.test.mjs | 18 ++++++++++++++++ scripts/next_extension_version.mjs | 21 +++++++++++++++---- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aa80317baf..0fc8af31e9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,8 +53,7 @@ concurrency: cancel-in-progress: false permissions: - contents: write # push version tags and create GitHub releases - id-token: write # required for OIDC / npm Trusted Publishers + contents: read env: ENVIRONMENT: ${{ secrets.ENVIRONMENT }} @@ -67,6 +66,8 @@ jobs: if: github.repository == 'prisma/language-tools' runs-on: ubuntu-latest timeout-minutes: 10 + permissions: + contents: write outputs: channel: ${{ steps.params.outputs.channel }} ref: ${{ steps.params.outputs.ref }} @@ -178,6 +179,9 @@ jobs: needs: [plan, test] runs-on: ubuntu-latest timeout-minutes: 10 + permissions: + contents: read + id-token: write env: NPM_CHANNEL: ${{ needs.plan.outputs.npm_channel }} VERSION: ${{ needs.plan.outputs.version }} @@ -209,6 +213,8 @@ jobs: needs: [plan, test] runs-on: ubuntu-latest timeout-minutes: 10 + permissions: + contents: write env: CHANNEL: ${{ needs.plan.outputs.channel }} NPM_CHANNEL: ${{ needs.plan.outputs.npm_channel }} diff --git a/scripts/__tests__/next-extension-version.test.mjs b/scripts/__tests__/next-extension-version.test.mjs index 9798ba5e3d..04395060b6 100644 --- a/scripts/__tests__/next-extension-version.test.mjs +++ b/scripts/__tests__/next-extension-version.test.mjs @@ -57,6 +57,24 @@ describe('releaseType', () => { it('throws on an unknown bump', () => { expect(() => releaseType({ channel: 'stable', bump: 'mega' })).toThrow() }) + + it('throws on a Prisma CLI version that is not a semantic version', () => { + expect(() => releaseType({ channel: 'stable', bump: 'auto', prismaVersion: 'invalid.0.0' })).toThrow( + /Invalid Prisma CLI version/, + ) + }) + + it('throws on a Prisma CLI prerelease for a stable release', () => { + expect(() => releaseType({ channel: 'stable', bump: 'auto', prismaVersion: '7.9.0-dev.4' })).toThrow(/prerelease/) + }) + + it('validates the Prisma CLI version even when the bump is explicit', () => { + expect(() => releaseType({ channel: 'stable', bump: 'minor', prismaVersion: 'invalid.0.0' })).toThrow() + }) + + it('does not validate the Prisma CLI version on the insider channel', () => { + expect(releaseType({ channel: 'insider', bump: 'auto', prismaVersion: 'anything' })).toEqual('patch') + }) }) describe('planRelease', () => { diff --git a/scripts/next_extension_version.mjs b/scripts/next_extension_version.mjs index daeeba42b1..42e7828853 100644 --- a/scripts/next_extension_version.mjs +++ b/scripts/next_extension_version.mjs @@ -22,6 +22,17 @@ export function latestReleasedVersion({ tags }) { return versions[0] } +function parseStablePrismaVersion(prismaVersion) { + const parsed = semVer.parse(prismaVersion) + if (parsed === null) { + throw new Error(`Invalid Prisma CLI version '${prismaVersion}'. Expected a semantic version such as 7.9.0.`) + } + if (parsed.prerelease.length > 0) { + throw new Error(`Prisma CLI version '${prismaVersion}' is a prerelease and can not be released on the stable channel.`) + } + return parsed +} + export function releaseType({ channel, bump = 'auto', prismaVersion = '' }) { if (channel === 'insider') { return 'patch' @@ -32,17 +43,19 @@ export function releaseType({ channel, bump = 'auto', prismaVersion = '' }) { if (!BUMPS.includes(bump)) { throw new Error(`Unknown bump '${bump}'. Expected one of: ${BUMPS.join(', ')}.`) } + + const prisma = prismaVersion === '' ? null : parseStablePrismaVersion(prismaVersion) + if (bump !== 'auto') { return bump } // 'auto' on stable mirrors the Prisma CLI release this extension ships: // x.0.0 -> major, x.y.0 -> minor, everything else (or no CLI bump) -> patch - if (prismaVersion !== '') { - const [, minor, patch] = prismaVersion.split('.') - if (minor === '0' && patch === '0') { + if (prisma !== null) { + if (prisma.minor === 0 && prisma.patch === 0) { return 'major' } - if (patch === '0') { + if (prisma.patch === 0) { return 'minor' } } From e393b9b00c6f5648ab00063c06cc2ee419f1a554 Mon Sep 17 00:00:00 2001 From: Steven McClankerton Date: Thu, 3 Sep 2026 10:18:28 +0000 Subject: [PATCH 5/6] Format next_extension_version.mjs and util.mjs with Prettier The prerelease error message exceeded the print width. util.mjs had a trailing blank line that already failed prettier --check on main; it is fixed here since the file is touched by this branch anyway. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01YJFVnTXe5iAtFHpARAg4ZU --- scripts/next_extension_version.mjs | 4 +++- scripts/util.mjs | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/next_extension_version.mjs b/scripts/next_extension_version.mjs index 42e7828853..0284f31234 100644 --- a/scripts/next_extension_version.mjs +++ b/scripts/next_extension_version.mjs @@ -28,7 +28,9 @@ function parseStablePrismaVersion(prismaVersion) { throw new Error(`Invalid Prisma CLI version '${prismaVersion}'. Expected a semantic version such as 7.9.0.`) } if (parsed.prerelease.length > 0) { - throw new Error(`Prisma CLI version '${prismaVersion}' is a prerelease and can not be released on the stable channel.`) + throw new Error( + `Prisma CLI version '${prismaVersion}' is a prerelease and can not be released on the stable channel.`, + ) } return parsed } diff --git a/scripts/util.mjs b/scripts/util.mjs index e2c5176880..7f0677015d 100644 --- a/scripts/util.mjs +++ b/scripts/util.mjs @@ -21,4 +21,3 @@ export function getPackageJsonContent({ path: filePath }) { export function writeJsonToPackageJson({ content, path: filePath }) { fs.writeFileSync(filePath, JSON.stringify(content, undefined, 2)) } - From 891d1feb947d5b0adce94e3d4f130f062377e13d Mon Sep 17 00:00:00 2001 From: Steven McClankerton Date: Thu, 3 Sep 2026 10:28:05 +0000 Subject: [PATCH 6/6] Restrict release refs and keep credentials out of the build jobs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three hardening changes from review. The plan job now rejects any ref other than main, stable or an x.y.x patch branch, so a manual dispatch can no longer release an arbitrary revision. The package job no longer holds contents: write. It builds the vsix and uploads it as before; a new release job downloads that artifact and creates the tag and GitHub release with nothing else in it — no checkout, no install, no build. The marketplace jobs now gate on release, so the publishing order is unchanged. Every checkout except the plan job's sets persist-credentials: false. Only plan performs git operations, so the test, build and publish jobs no longer have a git credential in their local config while running dependency code. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01YJFVnTXe5iAtFHpARAg4ZU --- .github/workflows/release.yml | 48 +++++++++++++++++++++++++++-------- docs/ci-cd.md | 21 ++++++++++++--- 2 files changed, 56 insertions(+), 13 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0fc8af31e9..4cbcb11658 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -95,6 +95,15 @@ jobs: if [ -z "$REF" ]; then if [ "$CHANNEL" = "stable" ]; then REF=stable; else REF=main; fi fi + case "$REF" in + main | stable) ;; + *) + if ! echo "$REF" | grep -Eq '^[0-9]+\.[0-9]+\.x$'; then + echo "Refusing to release from '$REF'. Releases run from main, stable or an x.y.x patch branch." >&2 + exit 1 + fi + ;; + esac fi { echo "channel=$CHANNEL" @@ -154,6 +163,7 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ needs.plan.outputs.sha }} + persist-credentials: false - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js @@ -190,6 +200,7 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ needs.plan.outputs.sha }} + persist-credentials: false - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js @@ -209,23 +220,20 @@ jobs: run: pnpm --filter @prisma/language-server publish --tag "$LS_NPM_TAG" --access public --no-git-checks package: - name: Build extension and create GitHub release + name: Build extension needs: [plan, test] runs-on: ubuntu-latest timeout-minutes: 10 - permissions: - contents: write env: CHANNEL: ${{ needs.plan.outputs.channel }} NPM_CHANNEL: ${{ needs.plan.outputs.npm_channel }} VERSION: ${{ needs.plan.outputs.version }} - TAG_NAME: ${{ needs.plan.outputs.tag_name }} ASSET_FILE: ${{ needs.plan.outputs.asset_name }}-${{ needs.plan.outputs.version }}.vsix - SHA: ${{ needs.plan.outputs.sha }} steps: - uses: actions/checkout@v4 with: ref: ${{ needs.plan.outputs.sha }} + persist-credentials: false - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js @@ -249,20 +257,38 @@ jobs: name: vsix path: packages/vscode/${{ env.ASSET_FILE }} if-no-files-found: error + + release: + name: Create GitHub release + needs: [plan, package] + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: write + env: + CHANNEL: ${{ needs.plan.outputs.channel }} + TAG_NAME: ${{ needs.plan.outputs.tag_name }} + ASSET_FILE: ${{ needs.plan.outputs.asset_name }}-${{ needs.plan.outputs.version }}.vsix + SHA: ${{ needs.plan.outputs.sha }} + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + steps: + - name: Download vsix artifact + uses: actions/download-artifact@v4 + with: + name: vsix - name: Create GitHub release - env: - GH_TOKEN: ${{ github.token }} run: | PRERELEASE="" if [ "$CHANNEL" = "insider" ]; then PRERELEASE="--prerelease"; fi - gh release create "$TAG_NAME" "packages/vscode/$ASSET_FILE" \ + gh release create "$TAG_NAME" "$ASSET_FILE" \ --title "$TAG_NAME" \ --target "$SHA" \ $PRERELEASE publish-marketplace: name: Publish to VS Code Marketplace - needs: [plan, package] + needs: [plan, release] runs-on: ubuntu-latest timeout-minutes: 10 env: @@ -272,6 +298,7 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ needs.plan.outputs.sha }} + persist-credentials: false - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js @@ -290,7 +317,7 @@ jobs: publish-open-vsx: name: Publish to open-vsx - needs: [plan, package] + needs: [plan, release] runs-on: ubuntu-latest timeout-minutes: 10 env: @@ -300,6 +327,7 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ needs.plan.outputs.sha }} + persist-credentials: false - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js diff --git a/docs/ci-cd.md b/docs/ci-cd.md index 78f83a59a2..2af4cd9184 100644 --- a/docs/ci-cd.md +++ b/docs/ci-cd.md @@ -29,9 +29,10 @@ graph TD PLAN[plan: resolve channel + branch, derive next version from git tags,
optionally commit Prisma CLI dependency bump] PLAN --> TEST[test: build, typecheck, LS unit tests, E2E tests
on ubuntu / macos / windows] TEST --> LS[publish-language-server:
npm publish with dist-tag dev or latest] - TEST --> PKG[package: build vsix, upload artifact,
create GitHub release + tag] - PKG --> MKT[publish-marketplace: vsce publish] - PKG --> OVSX[publish-open-vsx: ovsx publish] + TEST --> PKG[package: build vsix, upload artifact] + PKG --> REL[release: download artifact,
create GitHub release + tag] + REL --> MKT[publish-marketplace: vsce publish] + REL --> OVSX[publish-open-vsx: ovsx publish] end ``` @@ -42,9 +43,23 @@ graph TD VS Code E2E tests on all three OSes. - **package** builds the `.vsix` once; the same file is attached to the GitHub release and published to both marketplaces (passed as a workflow artifact). +- **release** only downloads that artifact and creates the tag and GitHub + release. It does not check out, install or build anything. - Insider GitHub releases are marked as pre-releases, so the repository's "latest release" always points to a stable version. +### Permissions + +The workflow default is `contents: read`. Write access is granted per job: +`plan` (pushes the dependency-bump commit and can reset `stable`), `release` +(creates the tag and GitHub release) and `publish-language-server` +(`id-token: write` for npm Trusted Publishers). Every checkout except `plan`'s +sets `persist-credentials: false`, so build and test steps never see a git +credential. + +Releases only run from `main`, `stable` or an `x.y.x` patch branch; `plan` +rejects any other `ref`. + ### Channels and branches | Channel | Branch | Extension name | LS npm dist-tag |