sveltejs/svelte

5 workflows · maturity 50% · 6 patterns · GitHub ↗

Security 40/100

Practices

✓ Matrix✓ Permissions○ Security scan○ AI review○ Cache✓ Concurrency○ Reusable workflows

Detected patterns

Security dimensions

permissions
25
security scan
0
supply chain
0
secret handling
15
harden runner
0

Workflows (5)

autofix perms .github/workflows/autofix.yml
Triggers
issue_comment, workflow_dispatch
Runs on
ubuntu-latest
Jobs
autofix-lint
Actions
pnpm/action-setup
Commands
  • pnpm install --frozen-lockfile
  • pnpm -F svelte build
  • pnpm format
  • git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git add -A git diff --staged --quiet || git commit -m "chore: autofix" git push origin HEAD
View raw YAML
name: Autofix Lint

on:
  issue_comment:
    types: [created]
  workflow_dispatch:

permissions: {}

jobs:
  autofix-lint:
    permissions:
      contents: write # to push the generated types commit
      pull-requests: read # to resolve the PR head ref
    # prevents this action from running on forks
    if: |
      github.repository == 'sveltejs/svelte' &&
      (
        github.event_name == 'workflow_dispatch' ||
        (
          github.event.issue.pull_request != null &&
          github.event.comment.body == '/autofix' &&
          contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association)
        )
      )
    runs-on: ubuntu-latest
    steps:
      - name: Get PR ref
        if: github.event_name != 'workflow_dispatch'
        id: pr
        uses: actions/github-script@v8
        with:
          script: |
            const { data: pull } = await github.rest.pulls.get({
              owner: context.repo.owner,
              repo: context.repo.repo,
              pull_number: context.issue.number
            });
            if (pull.head.repo.full_name !== `${context.repo.owner}/${context.repo.repo}`) {
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: context.issue.number,
                body: 'Cannot autofix: this PR is from a forked repository. The autofix workflow can only push to branches within this repository.'
              });
              core.setFailed('PR is from a fork');
            }
            core.setOutput('ref', pull.head.ref);
      - uses: actions/checkout@v6
        if: github.event_name == 'workflow_dispatch' || steps.pr.outcome == 'success'
        with:
          ref: ${{ github.event_name == 'workflow_dispatch' && github.ref || steps.pr.outputs.ref }}
      - uses: pnpm/action-setup@v4.3.0
      - uses: actions/setup-node@v6
        with:
          node-version: 24
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - name: Build
        run: pnpm -F svelte build
      - name: Run prettier
        run: pnpm format
      - name: Commit changes
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add -A
          git diff --staged --quiet || git commit -m "chore: autofix"
          git push origin HEAD
ci matrix perms .github/workflows/ci.yml
Triggers
push, pull_request
Runs on
${{ matrix.os }}, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest
Jobs
Tests, TestNoAsync, TSGo, Lint, Benchmarks
Matrix
include, include.node-version, include.os→ 18, 20, 22, 24, macOS-latest, ubuntu-latest, windows-latest
Actions
pnpm/action-setup, pnpm/action-setup, pnpm/action-setup, pnpm/action-setup, pnpm/action-setup
Commands
  • pnpm install --frozen-lockfile
  • pnpm playwright install chromium
  • pnpm test
  • pnpm install --frozen-lockfile
  • pnpm playwright install chromium
  • pnpm test runtime-runes
  • pnpm install --frozen-lockfile
  • cd packages/svelte && pnpm i -D @typescript/native-preview
View raw YAML
name: CI
on:
  push:
    branches: [main]
  pull_request:
permissions:
  contents: read # to fetch code (actions/checkout)

env:
  # We only install Chromium manually
  PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'

jobs:
  Tests:
    permissions: {}
    runs-on: ${{ matrix.os }}
    timeout-minutes: 15
    strategy:
      matrix:
        include:
          - node-version: 18
            os: windows-latest
          - node-version: 18
            os: macOS-latest
          - node-version: 18
            os: ubuntu-latest
          - node-version: 20
            os: ubuntu-latest
          - node-version: 22
            os: ubuntu-latest
          - node-version: 24
            os: ubuntu-latest

    steps:
      - uses: actions/checkout@v6
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v6
        with:
          node-version: ${{ matrix.node-version }}
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm playwright install chromium
      - run: pnpm test
        env:
          CI: true
  TestNoAsync:
    permissions: {}
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v6
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v6
        with:
          node-version: 22
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm playwright install chromium
      - run: pnpm test runtime-runes
        env:
          CI: true
          SVELTE_NO_ASYNC: true
  TSGo:
    permissions: {}
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - uses: actions/checkout@v6
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v6
        with:
          node-version: 24
          cache: pnpm
      - name: install
        run: pnpm install --frozen-lockfile
      - name: install tsgo
        run: cd packages/svelte && pnpm i -D @typescript/native-preview
      - name: type check
        run: cd packages/svelte && pnpm check:tsgo
  Lint:
    permissions: {}
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - uses: actions/checkout@v6
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v6
        with:
          node-version: 24
          cache: pnpm
      - name: install
        run: pnpm install --frozen-lockfile
      - name: type check
        run: pnpm check
      - name: lint
        if: (${{ success() }} || ${{ failure() }}) # ensures this step runs even if previous steps fail (avoids multiple runs uncovering different issues at different steps)
        run: pnpm lint
      - name: build and check generated types
        if: (${{ success() }} || ${{ failure() }}) # ensures this step runs even if previous steps fail
        run: pnpm build && { [ "`git status --porcelain=v1`" == "" ] || (echo "Generated types have changed — please regenerate types locally with `cd packages/svelte && pnpm generate:types` and commit the changes after you have reviewed them"; git diff; exit 1); }
  Benchmarks:
    permissions: {}
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v6
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v6
        with:
          node-version: 18
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm bench
        env:
          CI: true
ecosystem-ci-trigger perms .github/workflows/ecosystem-ci-trigger.yml
Triggers
issue_comment
Runs on
ubuntu-latest
Jobs
trigger
Actions
actions/create-github-app-token
View raw YAML
name: ecosystem-ci trigger

on:
  issue_comment:
    types: [created]

permissions: {}

jobs:
  trigger:
    runs-on: ubuntu-latest
    if: github.repository == 'sveltejs/svelte' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/ecosystem-ci run')
    permissions:
      issues: write # to add / delete reactions, post comments
      pull-requests: write # to read PR data, and to add labels
      actions: read # to check workflow status
      contents: read # to clone the repo
    steps:
      - name: Check User Permissions
        uses: actions/github-script@v8
        id: check-permissions
        with:
          script: |
            const user = context.payload.sender.login
            console.log(`Validate user: ${user}`)

            let hasTriagePermission = false
            try {
              const { data } = await github.rest.repos.getCollaboratorPermissionLevel({
                owner: context.repo.owner,
                repo: context.repo.repo,
                username: user,
              });
              hasTriagePermission = data.user.permissions.triage
            } catch (e) {
              console.warn(e)
            }

            if (hasTriagePermission) {
              console.log('User is allowed. Adding +1 reaction.')
              await github.rest.reactions.createForIssueComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                comment_id: context.payload.comment.id,
                content: '+1',
              })
            } else {
              console.log('User is not allowed. Adding -1 reaction.')
              await github.rest.reactions.createForIssueComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                comment_id: context.payload.comment.id,
                content: '-1',
              })
              throw new Error('User does not have the necessary permissions.')
            }

      - name: Get PR Data
        uses: actions/github-script@v8
        id: get-pr-data
        with:
          script: |
            console.log(`Get PR info: ${context.repo.owner}/${context.repo.repo}#${context.issue.number}`)
            const { data: pr } = await github.rest.pulls.get({
              owner: context.repo.owner,
              repo: context.repo.repo,
              pull_number: context.issue.number
            })

            const commentCreatedAt = new Date(context.payload.comment.created_at)
            const commitPushedAt = new Date(pr.head.repo.pushed_at)

            console.log(`Comment created at: ${commentCreatedAt.toISOString()}`)
            console.log(`PR last pushed at: ${commitPushedAt.toISOString()}`)

            // Check if any commits were pushed after the comment was created
            if (commitPushedAt > commentCreatedAt) {
              const errorMsg = [
                '⚠️ Security warning: PR was updated after the trigger command was posted.',
                '',
                `Comment posted at: ${commentCreatedAt.toISOString()}`,
                `PR last pushed at: ${commitPushedAt.toISOString()}`,
                '',
                'This could indicate an attempt to inject code after approval.',
                'Please review the latest changes and re-run /ecosystem-ci run if they are acceptable.'
              ].join('\n')

              core.setFailed(errorMsg)

              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: context.issue.number,
                body: errorMsg
              })

              throw new Error('PR was pushed to after comment was created')
            }

            return {
              num: context.issue.number,
              branchName: pr.head.ref,
              commit: pr.head.sha,
              repo: pr.head.repo.full_name
            }

      - name: Generate Token
        id: generate-token
        uses: actions/create-github-app-token@v2
        with:
          app-id: ${{ secrets.ECOSYSTEM_CI_GITHUB_APP_ID }}
          private-key: ${{ secrets.ECOSYSTEM_CI_GITHUB_APP_PRIVATE_KEY }}
          repositories: |
            svelte
            svelte-ecosystem-ci

      - name: Trigger Downstream Workflow
        uses: actions/github-script@v8
        id: trigger
        env:
          COMMENT: ${{ github.event.comment.body }}
          PR_DATA: ${{ steps.get-pr-data.outputs.result }}
        with:
          github-token: ${{ steps.generate-token.outputs.token }}
          script: |
            const comment = process.env.COMMENT.trim()
            const prData = JSON.parse(process.env.PR_DATA)

            const suite = comment.split('\n')[0].replace(/^\/ecosystem-ci run/, '').trim()

            await github.rest.actions.createWorkflowDispatch({
              owner: context.repo.owner,
              repo: 'svelte-ecosystem-ci',
              workflow_id: 'ecosystem-ci-from-pr.yml',
              ref: 'main',
              inputs: {
                prNumber: '' + prData.num,
                branchName: prData.branchName,
                repo: prData.repo,
                commit: prData.commit,
                suite: suite === '' ? '-' : suite
              }
            })
pkg.pr.new perms .github/workflows/pkg.pr.new.yml
Triggers
pull_request_target, push, workflow_dispatch
Runs on
ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest
Jobs
build, sanitize, comment, log
Actions
pnpm/action-setup
Commands
  • pnpm install --frozen-lockfile
  • pnpm build
  • pnpx pkg-pr-new publish --comment=off --json output.json --compact --no-template './packages/svelte'
View raw YAML
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@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));
release perms .github/workflows/release.yml
Triggers
push
Runs on
ubuntu-latest
Jobs
release
Actions
pnpm/action-setup, changesets/action
Commands
  • pnpm install --frozen-lockfile
  • pnpm build && { [ "`git status --porcelain=v1`" == "" ] || (echo "Generated types have changed — please regenerate types locally with `cd packages/svelte && pnpm generate:types` and commit the changes after you have reviewed them"; git diff; exit 1); }
View raw YAML
name: Release

on:
  push:
    branches:
      - main

concurrency:
  # prevent two release workflows from running at once
  # race conditions here can result in releases failing
  group: ${{ github.workflow }}

permissions: {}
jobs:
  release:
    # prevents this action from running on forks
    if: github.repository == 'sveltejs/svelte'
    permissions:
      contents: write # to create release (changesets/action)
      id-token: write # OpenID Connect token needed for provenance
      pull-requests: write # to create pull request (changesets/action)
    name: Release
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repo
        uses: actions/checkout@v6
        with:
          # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
          fetch-depth: 0
      - uses: pnpm/action-setup@v4
      - name: Setup Node.js
        uses: actions/setup-node@v6
        with:
          node-version: 24.x
          cache: pnpm

      - name: Install
        run: pnpm install --frozen-lockfile

      - name: Build
        run: pnpm build && { [ "`git status --porcelain=v1`" == "" ] || (echo "Generated types have changed — please regenerate types locally with `cd packages/svelte && pnpm generate:types` and commit the changes after you have reviewed them"; git diff; exit 1); }

      - name: Create Release Pull Request or Publish to npm
        id: changesets
        uses: changesets/action@v1
        with:
          version: pnpm changeset:version
          publish: pnpm changeset:publish
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_CONFIG_PROVENANCE: true