Version Packages (#18125) #11169
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: pkg.pr.new | |
| on: | |
| pull_request_target: | |
| types: [opened, synchronize] | |
| push: | |
| branches: [main] | |
| workflow_dispatch: | |
| inputs: | |
| sha: | |
| description: 'Commit SHA to build' | |
| required: true | |
| type: string | |
| pr: | |
| description: 'PR number to comment on' | |
| required: true | |
| type: number | |
| permissions: {} | |
| jobs: | |
| build: | |
| # Skip pull_request_target events from forks — maintainers can use workflow_dispatch instead | |
| if: > | |
| github.event_name != 'pull_request_target' || | |
| github.event.pull_request.head.repo.full_name == github.repository | |
| runs-on: ubuntu-latest | |
| # No permissions — this job runs user-controlled code | |
| permissions: {} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| # For pull_request_target, check out the PR head. | |
| # For workflow_dispatch, check out the manually specified SHA. | |
| # For push, fall back to the push SHA. | |
| ref: ${{ github.event.pull_request.head.sha || inputs.sha || github.sha }} | |
| - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22.x | |
| cache: pnpm | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Build | |
| run: pnpm build | |
| - run: pnpx pkg-pr-new publish --comment=off --json output.json --compact --no-template './packages/svelte' | |
| - name: Upload output | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: output | |
| path: ./output.json | |
| # Sanitizes the untrusted output from the build job before it's consumed by | |
| # jobs with elevated permissions. This ensures that only known package names | |
| # and valid SHA prefixes make it through. | |
| sanitize: | |
| needs: build | |
| runs-on: ubuntu-latest | |
| permissions: {} | |
| steps: | |
| - name: Download artifact | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: output | |
| - name: Sanitize output | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const raw = JSON.parse(fs.readFileSync('output.json', 'utf8')); | |
| const ALLOWED_PACKAGES = new Set(['svelte']); | |
| const SHA_PATTERN = /^[0-9a-f]{7}$/; | |
| const packages = (raw.packages || []) | |
| .filter(p => { | |
| if (!ALLOWED_PACKAGES.has(p.name)) { | |
| console.log(`Skipping unexpected package: ${JSON.stringify(p.name)}`); | |
| return false; | |
| } | |
| const sha = p.url?.replace(/^.+@([^@]+)$/, '$1'); | |
| if (!sha || !SHA_PATTERN.test(sha)) { | |
| console.log(`Skipping package with invalid SHA: ${JSON.stringify(p.url)}`); | |
| return false; | |
| } | |
| return true; | |
| }) | |
| .map(p => ({ | |
| name: p.name, | |
| sha: p.url.replace(/^.+@([^@]+)$/, '$1'), | |
| })); | |
| fs.writeFileSync('sanitized-output.json', JSON.stringify({ packages }), 'utf8'); | |
| - name: Upload sanitized output | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: sanitized-output | |
| path: ./sanitized-output.json | |
| comment: | |
| needs: sanitize | |
| if: github.event_name == 'pull_request_target' || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - name: Download sanitized artifact | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: sanitized-output | |
| - name: Resolve PR number | |
| id: pr | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| if (context.eventName === 'pull_request_target') { | |
| core.setOutput('number', context.issue.number); | |
| return; | |
| } | |
| // For workflow_dispatch, use the explicitly provided PR number. | |
| // We can't use listPullRequestsAssociatedWithCommit because fork | |
| // commits don't exist in the base repo, so the API returns nothing. | |
| const pr = Number('${{ inputs.pr }}'); | |
| if (!pr || isNaN(pr)) { | |
| core.setFailed('workflow_dispatch requires a valid pr input'); | |
| return; | |
| } | |
| core.setOutput('number', pr); | |
| - name: Post or update comment | |
| uses: actions/github-script@v8 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const fs = require('fs'); | |
| const { packages } = JSON.parse(fs.readFileSync('sanitized-output.json', 'utf8')); | |
| if (packages.length === 0) { | |
| console.log('No valid packages found. Skipping comment.'); | |
| return; | |
| } | |
| const issue_number = parseInt('${{ steps.pr.outputs.number }}', 10); | |
| const bot_comment_identifier = `<!-- pkg.pr.new comment -->`; | |
| const body = `${bot_comment_identifier} | |
| [Playground](https://svelte.dev/playground?version=pr-${issue_number}) | |
| \`\`\` | |
| ${packages.map(p => `pnpm add https://pkg.pr.new/${p.name}@${issue_number}`).join('\n')} | |
| \`\`\` | |
| `; | |
| const comments = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number, | |
| }); | |
| const existing = comments.data.find(c => c.body.includes(bot_comment_identifier)); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existing.id, | |
| body, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number, | |
| body, | |
| }); | |
| } | |
| log: | |
| needs: sanitize | |
| if: github.event_name == 'push' | |
| runs-on: ubuntu-latest | |
| permissions: {} | |
| steps: | |
| - name: Download sanitized artifact | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: sanitized-output | |
| - name: Log publish info | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const { packages } = JSON.parse(fs.readFileSync('sanitized-output.json', 'utf8')); | |
| if (packages.length === 0) { | |
| console.log('No valid packages found.'); | |
| return; | |
| } | |
| console.log('\n' + '='.repeat(50)); | |
| console.log('Publish Information'); | |
| console.log('='.repeat(50)); | |
| for (const p of packages) { | |
| console.log(`${p.name} - pnpm add https://pkg.pr.new/${p.name}@${p.sha}`); | |
| } | |
| const svelte = packages.find(p => p.name === 'svelte'); | |
| if (svelte) { | |
| console.log(`\nPlayground: https://svelte.dev/playground?version=commit-${svelte.sha}`); | |
| } | |
| console.log('='.repeat(50)); |