mermaid-js/mermaid

21 workflows · maturity 83% · 10 patterns · GitHub ↗

Security 54.4/100

Practices

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

Detected patterns

Security dimensions

permissions
20.2
security scan
20.8
supply chain
13.3
secret handling
0
harden runner
0

Tools: github/codeql-action/analyze, github/codeql-action/autobuild, github/codeql-action/init, github/codeql-action/upload-sarif, ossf/scorecard-action

Workflows (21)

autofix perms .github/workflows/autofix.yml
Triggers
pull_request
Runs on
ubuntu-latest
Jobs
autofix
Actions
pnpm/action-setup, autofix-ci/action
Commands
  • pnpm install --frozen-lockfile
  • pnpm -w run lint:fix
  • pnpm run --filter mermaid types:build-config
  • pnpm run docs:build
View raw YAML
name: autofix.ci # needed to securely identify the workflow

on:
  pull_request:
    branches-ignore:
      - 'renovate/**'
permissions:
  contents: read

concurrency: ${{ github.workflow }}-${{ github.ref }}

jobs:
  autofix:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

      - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
        # uses version from "packageManager" field in package.json

      - name: Setup Node.js
        uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
        with:
          cache: pnpm
          node-version-file: '.node-version'

      - name: Install Packages
        run: |
          pnpm install --frozen-lockfile
        env:
          CYPRESS_CACHE_FOLDER: .cache/Cypress

      - name: Fix Linting
        shell: bash
        run: pnpm -w run lint:fix

      - name: Sync `./src/config.type.ts` with `./src/schemas/config.schema.yaml`
        shell: bash
        run: pnpm run --filter mermaid types:build-config

      - name: Build Docs
        working-directory: ./packages/mermaid
        run: pnpm run docs:build

      - uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27 # main
build-docs perms .github/workflows/build-docs.yml
Triggers
push, pull_request, merge_group
Runs on
ubuntu-latest
Jobs
build-docs
Actions
pnpm/action-setup
Commands
  • pnpm install --frozen-lockfile
  • pnpm --filter mermaid run docs:verify-version
  • pnpm --filter mermaid run docs:build:vitepress
View raw YAML
name: Build Vitepress docs

on:
  push:
    branches:
      - master
      - release/*
  pull_request:
  merge_group:

concurrency: ${{ github.workflow }}-${{ github.ref }}

permissions:
  contents: read

jobs:
  build-docs:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

      - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0

      - name: Setup Node.js
        uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
        with:
          cache: pnpm
          node-version-file: '.node-version'

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

      - name: Verify release version
        if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release')) }}
        run: pnpm --filter mermaid run docs:verify-version

      - name: Run Build
        run: pnpm --filter mermaid run docs:build:vitepress
check-readme-in-sync perms .github/workflows/check-readme-in-sync.yml
Triggers
push, pull_request
Runs on
ubuntu-latest
Jobs
check-readme
Commands
  • if [ -z "$(diff README.md docs/README.md --brief)" ] then echo "README.md and docs/README.md are in sync" else echo "Make sure that README.md and docs/README.md are in sync" echo echo "Difference:" echo diff README.md docs/README.md -u exit 1 fi
View raw YAML
# Reference: https://github.com/Yash-Singh1/eslint-plugin-userscripts/blob/main/.github/workflows/readme-in-sync.yml

name: Check if README and docs/README are in sync

on:
  push:
    branches:
      - gh-pages
  pull_request:
    branches:
      - gh-pages

permissions:
  contents: read

jobs:
  check-readme:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

      - name: Check for difference in README.md and docs/README.md
        run: |
          if [ -z "$(diff README.md docs/README.md --brief)" ]
          then
            echo "README.md and docs/README.md are in sync"
          else
            echo "Make sure that README.md and docs/README.md are in sync"
            echo
            echo "Difference:"
            echo
            diff README.md docs/README.md -u
            exit 1
          fi
codeql matrix perms security .github/workflows/codeql.yml
Triggers
push, pull_request
Runs on
ubuntu-latest
Jobs
analyze
Matrix
language→ actions, javascript
Actions
github/codeql-action/init, github/codeql-action/autobuild, github/codeql-action/analyze
View raw YAML
name: 'CodeQL'

on:
  push:
    branches: [develop]
  pull_request:
    # The branches below must be a subset of the branches above
    branches: [develop]
    types:
      - opened
      - synchronize
      - ready_for_review

permissions: # added using https://github.com/step-security/secure-repo
  contents: read

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: read
      security-events: write

    strategy:
      fail-fast: false
      matrix:
        language: ['javascript', 'actions']
        # CodeQL supports [ 'actions', 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
        # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support

    steps:
      - name: Checkout repository
        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

      # Initializes the CodeQL tools for scanning.
      - name: Initialize CodeQL
        uses: github/codeql-action/init@5378192d256ef1302a6980fffe5ca04426d43091 # v3.28.21
        with:
          config-file: ./.github/codeql/codeql-config.yml
          languages: ${{ matrix.language }}
          # If you wish to specify custom queries, you can do so here or in a config file.
          # By default, queries listed here will override any specified in a config file.
          # Prefix the list here with "+" to use these queries and those in the config file.
          # queries: ./path/to/local/query, your-org/your-repo/queries@main

      # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
      # If this step fails, then you should remove it and run the build manually (see below)
      - name: Autobuild
        uses: github/codeql-action/autobuild@5378192d256ef1302a6980fffe5ca04426d43091 # v3.28.21

      # ℹ️ Command-line programs to run using the OS shell.
      # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun

      # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
      #    and modify them (or add more) to build your code if your project
      #    uses a compiled language

      #- run: |
      #   make bootstrap
      #   make release

      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@5378192d256ef1302a6980fffe5ca04426d43091 # v3.28.21
dependency-review perms .github/workflows/dependency-review.yml
Triggers
pull_request
Runs on
ubuntu-latest
Jobs
dependency-review
Actions
actions/dependency-review-action
View raw YAML
# Dependency Review Action
#
# This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging.
#
# Source repository: https://github.com/actions/dependency-review-action
# Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement
name: 'Dependency Review'
on: [pull_request]

permissions:
  contents: read

jobs:
  dependency-review:
    runs-on: ubuntu-latest
    steps:
      - name: 'Checkout Repository'
        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
      - name: 'Dependency Review'
        uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4.5.0
e2e matrix perms .github/workflows/e2e.yml
Triggers
push, pull_request, merge_group
Runs on
ubuntu-latest, ubuntu-latest
Jobs
cache, e2e
Matrix
containers→ 1, 2, 3, 4, 5
Actions
pnpm/action-setup, cypress-io/github-action, pnpm/action-setup, cypress-io/github-action, cypress-io/github-action, codecov/codecov-action
Commands
  • pnpm run build:viz mkdir -p cypress/snapshots/stats/base mv stats cypress/snapshots/stats/base
  • pnpm run build:viz mv stats cypress/snapshots/stats/head echo '## Bundle size difference' >> "$GITHUB_STEP_SUMMARY" echo '' >> "$GITHUB_STEP_SUMMARY" npx tsx scripts/size.ts >> "$GITHUB_STEP_SUMMARY"
View raw YAML
name: E2E

on:
  push:
    branches:
      - develop
      - master
      - release/**
  pull_request:
  merge_group:

concurrency: ${{ github.workflow }}-${{ github.ref }}

permissions:
  contents: read

env:
  # For PRs and MergeQueues, the target commit is used, and for push events to non-develop branches, github.event.previous is used if available. Otherwise, 'develop' is used.
  targetHash: >-
    ${{ 
      github.event.pull_request.base.sha || 
      github.event.merge_group.base_sha || 
      (
        (
          (github.event_name == 'push' && github.ref == 'refs/heads/develop') || 
          github.event.before == '0000000000000000000000000000000000000000'
        ) && 'develop'
      ) || 
      github.event.before
    }}
  RUN_VISUAL_TEST: >-
    ${{ github.repository == 'mermaid-js/mermaid' && (github.event_name != 'pull_request' || !startsWith(github.head_ref, 'renovate/')) }}
jobs:
  cache:
    runs-on: ubuntu-latest
    container:
      image: cypress/browsers:node-20.16.0-chrome-127.0.6533.88-1-ff-128.0.3-edge-127.0.2651.74-1
      options: --user 1001
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
      - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
      - name: Setup Node.js
        uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
        with:
          node-version-file: '.node-version'
      - name: Cache snapshots
        id: cache-snapshot
        uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
        with:
          path: ./cypress/snapshots
          key: ${{ runner.os }}-snapshots-${{ env.targetHash }}

      # If a snapshot for a given Hash is not found, we checkout that commit, run the tests and cache the snapshots.
      - name: Switch to base branch
        if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }}
        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
        with:
          ref: ${{ env.targetHash }}

      - name: Install dependencies
        if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }}
        uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16
        with:
          # just perform install
          runTests: false

      - name: Calculate bundle size
        if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true'}}
        run: |
          pnpm run build:viz
          mkdir -p cypress/snapshots/stats/base
          mv stats cypress/snapshots/stats/base

  e2e:
    runs-on: ubuntu-latest
    container:
      image: cypress/browsers:node-20.16.0-chrome-127.0.6533.88-1-ff-128.0.3-edge-127.0.2651.74-1
      options: --user 1001
    needs: cache
    strategy:
      fail-fast: false
      matrix:
        containers: [1, 2, 3, 4, 5]
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

      - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
        # uses version from "packageManager" field in package.json

      - name: Setup Node.js
        uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
        with:
          node-version-file: '.node-version'

      # These cached snapshots are downloaded, providing the reference snapshots.
      - name: Cache snapshots
        id: cache-snapshot
        uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
        with:
          path: ./cypress/snapshots
          key: ${{ runner.os }}-snapshots-${{ env.targetHash }}

      - name: Install dependencies
        uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16
        with:
          runTests: false

      - name: Output size diff
        if: ${{ matrix.containers == 1 }}
        run: |
          pnpm run build:viz
          mv stats cypress/snapshots/stats/head
          echo '## Bundle size difference' >> "$GITHUB_STEP_SUMMARY"
          echo '' >> "$GITHUB_STEP_SUMMARY"
          npx tsx scripts/size.ts >> "$GITHUB_STEP_SUMMARY"

      # Install NPM dependencies, cache them correctly
      # and run all Cypress tests
      - name: Cypress run
        uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16
        id: cypress
        with:
          install: false
          start: pnpm run dev:coverage
          wait-on: 'http://localhost:9000'
          browser: chrome
          # Disable recording if we don't have an API key
          # e.g. if this action was run from a fork
          record: ${{ env.RUN_VISUAL_TEST == 'true' && secrets.CYPRESS_RECORD_KEY != '' }}
        env:
          ARGOS_PARALLEL: ${{ env.RUN_VISUAL_TEST == 'true' }}
          ARGOS_PARALLEL_TOTAL: ${{ env.RUN_VISUAL_TEST == 'true' && strategy.job-total || 1 }}
          ARGOS_PARALLEL_INDEX: ${{ env.RUN_VISUAL_TEST == 'true' && matrix.containers || 1 }}
          CYPRESS_COMMIT: ${{ github.sha }}
          CYPRESS_RECORD_KEY: ${{ env.RUN_VISUAL_TEST == 'true' && secrets.CYPRESS_RECORD_KEY || ''}}
          SPLIT: ${{ strategy.job-total }}
          SPLIT_INDEX: ${{ strategy.job-index }}
          SPLIT_FILE: 'cypress/timings.json'
          VITEST_COVERAGE: true

      - name: Upload Coverage to Codecov
        uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5.3.1
        # Run step only pushes to develop and pull_requests
        if: ${{ steps.cypress.conclusion == 'success' && (github.event_name == 'pull_request' || github.ref == 'refs/heads/develop')}}
        with:
          files: coverage/cypress/lcov.info
          flags: e2e
          name: mermaid-codecov
          fail_ci_if_error: false
          verbose: true
          token: 6845cc80-77ee-4e17-85a1-026cd95e0766
e2e-applitools perms .github/workflows/e2e-applitools.yml
Triggers
workflow_dispatch
Runs on
ubuntu-latest
Jobs
e2e-applitools
Actions
pnpm/action-setup, wei/curl, cypress-io/github-action
Commands
  • echo "::error,title=Not using Applitools::APPLITOOLS_API_KEY is empty, disabling Applitools for this run."
View raw YAML
name: E2E (Applitools)

on:
  workflow_dispatch:
    # Because we want to limit Applitools usage, so we only want to start this
    # workflow on rare occasions/manually.
    inputs:
      parent_branch:
        required: true
        type: string
        default: master
        description: 'Parent branch to use for PRs'

concurrency: ${{ github.workflow }}-${{ github.ref }}

permissions:
  contents: read

env:
  # on PRs from forks, this secret will always be empty, for security reasons
  USE_APPLI: ${{ secrets.APPLITOOLS_API_KEY && 'true' || '' }}

jobs:
  e2e-applitools:
    runs-on: ubuntu-latest
    steps:
      - if: ${{ ! env.USE_APPLI }}
        name: Warn if not using Applitools
        run: |
          echo "::error,title=Not using Applitools::APPLITOOLS_API_KEY is empty, disabling Applitools for this run."

      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

      - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
        # uses version from "packageManager" field in package.json

      - name: Setup Node.js
        uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
        with:
          node-version-file: '.node-version'

      - if: ${{ env.USE_APPLI }}
        name: Notify applitools of new batch
        # Copied from docs https://applitools.com/docs/topics/integrations/github-integration-ci-setup.html
        env:
          # e.g. mermaid-js/mermaid/my-branch
          APPLITOOLS_BRANCH: ${{ github.repository }}/${{ github.ref_name }}
          APPLITOOLS_PARENT_BRANCH: ${{ github.event.inputs.parent_branch }}
          APPLITOOLS_API_KEY: ${{ secrets.APPLITOOLS_API_KEY }}
          APPLITOOLS_SERVER_URL: 'https://eyesapi.applitools.com'
        uses: wei/curl@012398a392d02480afa2720780031f8621d5f94c
        with:
          args: -X POST "$APPLITOOLS_SERVER_URL/api/externals/github/push?apiKey=$APPLITOOLS_API_KEY&CommitSha=$GITHUB_SHA&BranchName=${APPLITOOLS_BRANCH}$&ParentBranchName=$APPLITOOLS_PARENT_BRANCH"

      - name: Cypress run
        uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16
        id: cypress
        with:
          start: pnpm run dev
          wait-on: 'http://localhost:9000'
        env:
          # Mermaid applitools.config.js uses this to pick batch name.
          APPLI_BRANCH: ${{ github.ref_name }}
          APPLITOOLS_BATCH_ID: ${{ github.sha }}
          # e.g. mermaid-js/mermaid/my-branch
          APPLITOOLS_BRANCH: ${{ github.repository }}/${{ github.ref_name }}
          APPLITOOLS_PARENT_BRANCH: ${{ github.event.inputs.parent_branch }}
          APPLITOOLS_API_KEY: ${{ secrets.APPLITOOLS_API_KEY }}
          APPLITOOLS_SERVER_URL: 'https://eyesapi.applitools.com'
e2e-timings perms .github/workflows/e2e-timings.yml
Triggers
schedule, workflow_dispatch
Runs on
ubuntu-latest
Jobs
timings
Actions
pnpm/action-setup, cypress-io/github-action, cypress-io/github-action, peter-evans/create-pull-request
Commands
  • OUTPUT=$(pnpm tsx scripts/compare-timings.ts) echo "$OUTPUT" >> $GITHUB_STEP_SUMMARY echo "output<<EOF" >> $GITHUB_OUTPUT echo "$OUTPUT" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT
View raw YAML
name: E2E - Generate Timings

on:
  # run this workflow every night at 3am
  schedule:
    - cron: '28 3 * * *'
  # or when the user triggers it from GitHub Actions page
  workflow_dispatch:

concurrency: ${{ github.workflow }}-${{ github.ref }}

permissions:
  contents: write
  pull-requests: write

jobs:
  timings:
    runs-on: ubuntu-latest
    container:
      image: cypress/browsers:node-20.16.0-chrome-127.0.6533.88-1-ff-128.0.3-edge-127.0.2651.74-1
      options: --user 1001
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
      - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
      - name: Setup Node.js
        uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
        with:
          node-version-file: '.node-version'
      - name: Install dependencies
        uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16
        with:
          runTests: false

      - name: Cypress run
        uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16
        id: cypress
        with:
          install: false
          start: pnpm run dev:coverage
          wait-on: 'http://localhost:9000'
          browser: chrome
          publish-summary: false
        env:
          VITEST_COVERAGE: true
          CYPRESS_COMMIT: ${{ github.sha }}
          SPLIT: 1
          SPLIT_INDEX: 0
          SPLIT_FILE: 'cypress/timings.json'

      - name: Compare timings
        id: compare
        run: |
          OUTPUT=$(pnpm tsx scripts/compare-timings.ts)
          echo "$OUTPUT" >> $GITHUB_STEP_SUMMARY

          echo "output<<EOF" >> $GITHUB_OUTPUT
          echo "$OUTPUT" >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT

      - name: Commit and create pull request
        uses: peter-evans/create-pull-request@2271f1ddcf09437ed8f019733eb5cfba58ac76f0
        with:
          add-paths: |
            cypress/timings.json
          commit-message: 'chore: update E2E timings'
          branch: update-timings
          title: Update E2E Timings
          body: ${{ steps.compare.outputs.output }}
          delete-branch: true
          sign-commits: true
issue-triage perms .github/workflows/issue-triage.yml
Triggers
issues
Runs on
ubuntu-latest
Jobs
triage
Actions
andymckay/labeler
View raw YAML
name: Apply triage label to new issue

on:
  issues:
    types: [opened]

permissions: # added using https://github.com/step-security/secure-repo
  contents: read

jobs:
  triage:
    permissions:
      issues: write # for andymckay/labeler to label issues
      pull-requests: write # for andymckay/labeler to label PRs
    runs-on: ubuntu-latest
    steps:
      - uses: andymckay/labeler@e6c4322d0397f3240f0e7e30a33b5c5df2d39e90 # 1.0.4
        with:
          repo-token: '${{ secrets.GITHUB_TOKEN }}'
          add-labels: 'Status: Triage'
link-checker perms .github/workflows/link-checker.yml
Triggers
push, pull_request, workflow_dispatch, schedule
Runs on
ubuntu-latest
Jobs
link-checker
Actions
lycheeverse/lychee-action
View raw YAML
# This Link Checker is run on all documentation files once per week.

# references:
# - https://github.com/lycheeverse/lychee-action
# - https://github.com/lycheeverse/lychee

name: Link Checker

on:
  push:
    branches:
      - develop
      - master
  pull_request:
    branches:
      - master
  workflow_dispatch:
  schedule:
    # * is a special character in YAML so you have to quote this string
    - cron: '30 8 * * *'

permissions: # added using https://github.com/step-security/secure-repo
  contents: read

jobs:
  link-checker:
    runs-on: ubuntu-latest
    permissions:
      # lychee only uses the GITHUB_TOKEN to avoid rate-limiting
      contents: read
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

      - name: Restore lychee cache
        uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
        with:
          path: .lycheecache
          key: cache-lychee-${{ github.sha }}
          restore-keys: cache-lychee-

      - name: Link Checker
        uses: lycheeverse/lychee-action@f613c4a64e50d792e0b31ec34bbcbba12263c6a6 # v2.3.0
        with:
          args: >-
            --config .github/lychee.toml
            packages/mermaid/src/docs/**/*.md
            README.md
            README.zh-CN.md
          fail: true
          jobSummary: true
        env:
          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
lint perms .github/workflows/lint.yml
Triggers
push, merge_group, pull_request, workflow_dispatch
Runs on
ubuntu-latest, ubuntu-latest
Jobs
docker-lint, lint
Actions
hadolint/hadolint-action, pnpm/action-setup, testomatio/check-tests
Commands
  • pnpm install --frozen-lockfile
  • if ! pnpm run lint; then # print a nice error message on lint failure ERROR_MESSAGE='Running `pnpm run lint` failed.' ERROR_MESSAGE+=' Running `pnpm -w run lint:fix` may fix this issue. ' ERROR_MESSAGE+=" If this error doesn't occur on your local machine," ERROR_MESSAGE+=' make sure your packages are up-to-date by running `pnpm install`.' ERROR_MESSAGE+=' You may also need to delete your prettier cache by running' ERROR_MESSAGE+=' `rm ./node_modules/.cache/prettier/.prettier-cache`.' echo "::error title=Lint failure::${ERROR_MESSAGE}" # make sure to return an error exitcode so that GitHub actions shows a red-cross exit 1 fi
  • if ! pnpm run --filter mermaid types:verify-config; then ERROR_MESSAGE='Running `pnpm run --filter mermaid types:verify-config` failed.' ERROR_MESSAGE+=' This should be fixed by running' ERROR_MESSAGE+=' `pnpm run --filter mermaid types:build-config`' ERROR_MESSAGE+=' on your local machine.' echo "::error title=Lint failure::${ERROR_MESSAGE}" # make sure to return an error exitcode so that GitHub actions shows a red-cross exit 1 fi
  • if ! pnpm run --filter mermaid checkCircle; then ERROR_MESSAGE='Circular dependency detected.' ERROR_MESSAGE+=' This should be fixed by removing the circular dependency.' ERROR_MESSAGE+=' Run `pnpm run --filter mermaid checkCircle` on your local machine' ERROR_MESSAGE+=' to see the circular dependency.' echo "::error title=Lint failure::${ERROR_MESSAGE}" # make sure to return an error exitcode so that GitHub actions shows a red-cross exit 1 fi
  • pnpm run docs:verify
View raw YAML
name: Lint

on:
  push:
  merge_group:
  pull_request:
  workflow_dispatch:

concurrency: ${{ github.workflow }}-${{ github.ref }}

permissions:
  contents: write

jobs:
  docker-lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

      - uses: hadolint/hadolint-action@54c9adbab1582c2ef04b2016b760714a4bfde3cf # v3.1.0
        with:
          verbose: true
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

      - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
        # uses version from "packageManager" field in package.json

      - name: Setup Node.js
        uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
        with:
          cache: pnpm
          node-version-file: '.node-version'

      - name: Install Packages
        run: |
          pnpm install --frozen-lockfile
        env:
          CYPRESS_CACHE_FOLDER: .cache/Cypress

      - name: Run Linting
        shell: bash
        run: |
          if ! pnpm run lint; then
              # print a nice error message on lint failure
              ERROR_MESSAGE='Running `pnpm run lint` failed.'
              ERROR_MESSAGE+=' Running `pnpm -w run lint:fix` may fix this issue. '
              ERROR_MESSAGE+=" If this error doesn't occur on your local machine,"
              ERROR_MESSAGE+=' make sure your packages are up-to-date by running `pnpm install`.'
              ERROR_MESSAGE+=' You may also need to delete your prettier cache by running'
              ERROR_MESSAGE+=' `rm ./node_modules/.cache/prettier/.prettier-cache`.'
              echo "::error title=Lint failure::${ERROR_MESSAGE}"
              # make sure to return an error exitcode so that GitHub actions shows a red-cross
              exit 1
          fi

      - name: Verify `./src/config.type.ts` is in sync with `./src/schemas/config.schema.yaml`
        shell: bash
        run: |
          if ! pnpm run --filter mermaid types:verify-config; then
            ERROR_MESSAGE='Running `pnpm run --filter mermaid types:verify-config` failed.'
            ERROR_MESSAGE+=' This should be fixed by running'
            ERROR_MESSAGE+=' `pnpm run --filter mermaid types:build-config`'
            ERROR_MESSAGE+=' on your local machine.'
            echo "::error title=Lint failure::${ERROR_MESSAGE}"
            # make sure to return an error exitcode so that GitHub actions shows a red-cross
            exit 1
          fi

      - name: Verify no circular dependencies
        working-directory: ./packages/mermaid
        shell: bash
        run: |
          if ! pnpm run --filter mermaid checkCircle; then
            ERROR_MESSAGE='Circular dependency detected.'
            ERROR_MESSAGE+=' This should be fixed by removing the circular dependency.'
            ERROR_MESSAGE+=' Run `pnpm run --filter mermaid checkCircle` on your local machine'
            ERROR_MESSAGE+=' to see the circular dependency.'
            echo "::error title=Lint failure::${ERROR_MESSAGE}"
            # make sure to return an error exitcode so that GitHub actions shows a red-cross
            exit 1
          fi

      - name: Verify Docs
        id: verifyDocs
        working-directory: ./packages/mermaid
        continue-on-error: ${{ github.event_name == 'push' }}
        run: pnpm run docs:verify

      - uses: testomatio/check-tests@0ea638fcec1820cf2e7b9854fdbdd04128a55bd4 # stable
        with:
          framework: cypress
          tests: './cypress/e2e/**/**.spec.js'
          token: ${{ secrets.GITHUB_TOKEN }}
          has-tests-label: true
pr-labeler perms .github/workflows/pr-labeler.yml
Triggers
pull_request_target
Runs on
ubuntu-latest
Jobs
pr-labeler
Actions
release-drafter/release-drafter
View raw YAML
name: Apply labels to PR
on:
  pull_request_target:
    # required for pr-labeler to support PRs from forks
    # ===================== ⛔ ☢️ 🚫 ⚠️ Warning ⚠️ 🚫 ☢️ ⛔ =======================
    # Be very careful what you put in this GitHub Action workflow file to avoid
    # malicious PRs from getting access to the Mermaid-js repo.
    #
    # Please read the following first before reviewing/merging:
    # - https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target
    # - https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
    types: [opened, reopened, synchronize]

permissions:
  contents: read

jobs:
  pr-labeler:
    runs-on: ubuntu-latest
    permissions:
      contents: read # read permission is required to read config file
      pull-requests: write # write permission is required to label PRs
    steps:
      - name: Label PR
        uses: release-drafter/release-drafter@b1476f6e6eb133afa41ed8589daba6dc69b4d3f5 # v6.1.0
        with:
          config-name: pr-labeler.yml
          disable-autolabeler: false
          disable-releaser: true
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Add "Sponsored by MermaidChart" label
        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const prNumber = context.payload.pull_request.number;
            const { data: commits } = await github.rest.pulls.listCommits({
              owner: context.repo.owner,
              repo: context.repo.repo,
              pull_number: prNumber,
            });

            const isSponsored = commits.every(
              (c) => c.commit.author.email?.endsWith('@mermaidchart.com')
            );

            if (isSponsored) {
              console.log('PR is sponsored. Adding label.');
              await github.rest.issues.addLabels({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: prNumber,
                labels: ['Sponsored by MermaidChart'],
              });
            }
publish-docs perms .github/workflows/publish-docs.yml
Triggers
push
Runs on
ubuntu-latest, ubuntu-latest
Jobs
build-docs, deploy-docs
Actions
pnpm/action-setup, actions/configure-pages, actions/upload-pages-artifact, actions/deploy-pages
Commands
  • pnpm install --frozen-lockfile
  • pnpm --filter mermaid run docs:build:vitepress
View raw YAML
name: Deploy Vitepress docs to Pages

on:
  # Runs on pushes targeting the default branch
  push:
    branches:
      - master

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
  contents: read
  pages: write
  id-token: write

# Allow one concurrent deployment
concurrency:
  group: 'pages'
  cancel-in-progress: true

jobs:
  # Build job
  build-docs:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

      - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0

      - name: Setup Node.js
        uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
        with:
          cache: pnpm
          node-version-file: '.node-version'

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

      - name: Setup Pages
        uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0

      - name: Run Build
        run: pnpm --filter mermaid run docs:build:vitepress

      - name: Upload artifact
        uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1
        with:
          path: packages/mermaid/src/vitepress/.vitepress/dist

  # Deployment job
  deploy-docs:
    environment:
      name: github-pages
    runs-on: ubuntu-latest
    needs: build-docs
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
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 npm install -g npm@11
View raw YAML
name: Release

on:
  push:
    branches:
      - master

concurrency: ${{ github.workflow }}-${{ github.ref }}

permissions: # added using https://github.com/step-security/secure-repo
  contents: read

jobs:
  release:
    if: github.repository == 'mermaid-js/mermaid'
    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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

      - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0

      - name: Setup Node.js
        uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
        with:
          cache: pnpm
          node-version-file: '.node-version'

      - name: Install Packages
        run: |
          pnpm install --frozen-lockfile
          npm install -g npm@11

      - name: Create Release Pull Request or Publish to npm
        id: changesets
        uses: changesets/action@06245a4e0a36c064a573d4150030f5ec548e4fcc # v1.4.10
        with:
          version: pnpm changeset:version
          publish: pnpm changeset:publish
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_CONFIG_PROVENANCE: true
release-preview perms .github/workflows/release-preview.yml
Triggers
pull_request
Runs on
ubuntu-latest
Jobs
preview
Actions
pnpm/action-setup
Commands
  • pnpm install --frozen-lockfile
  • pnpx pkg-pr-new publish --pnpm './packages/*'
View raw YAML
name: Preview release

on:
  pull_request:
    branches: [develop]
    types: [opened, synchronize, labeled, ready_for_review]

concurrency:
  group: ${{ github.workflow }}-${{ github.event.number }}
  cancel-in-progress: true

permissions:
  contents: read
  actions: write

jobs:
  preview:
    if: ${{ github.repository_owner == 'mermaid-js' }}
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write
      issues: write
      pull-requests: write
    name: Publish preview release
    timeout-minutes: 5
    steps:
      - name: Checkout Repo
        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

      - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0

      - name: Setup Node.js
        uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
        with:
          cache: pnpm
          node-version-file: '.node-version'

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

      - name: Publish packages
        run: pnpx pkg-pr-new publish --pnpm './packages/*'
release-preview-publish .github/workflows/release-preview-publish.yml
Triggers
push
Runs on
ubuntu-latest
Jobs
publish-preview
Actions
pnpm/action-setup
Commands
  • pnpm install --frozen-lockfile
  • npm i json@11.0.0 --global
  • PREVIEW_VERSION=$(git log --oneline "origin/$GITHUB_REF_NAME" ^"origin/master" | wc -l) VERSION=$(echo ${{github.ref}} | tail -c +20)-preview.$PREVIEW_VERSION echo $VERSION npm version --no-git-tag-version --allow-same-version $VERSION npm set //npm.pkg.github.com/:_authToken ${{ secrets.GITHUB_TOKEN }} npm set registry https://npm.pkg.github.com/mermaid-js json -I -f package.json -e 'this.name="@mermaid-js/mermaid"' # Package name needs to be set to a scoped one because GitHub registry requires this json -I -f package.json -e 'this.repository="git://github.com/mermaid-js/mermaid"' # Repo url needs to have a specific format too npm publish
View raw YAML
name: Publish release preview package

on:
  push:
    branches:
      - 'release/**'

jobs:
  publish-preview:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
        with:
          fetch-depth: 0

      - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0

      - name: Setup Node.js
        uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
        with:
          cache: pnpm
          node-version-file: '.node-version'

      - name: Install Packages
        run: |
          pnpm install --frozen-lockfile
        env:
          CYPRESS_CACHE_FOLDER: .cache/Cypress

      - name: Install Json
        run: npm i json@11.0.0 --global

      - name: Publish
        working-directory: ./packages/mermaid
        run: |
          PREVIEW_VERSION=$(git log --oneline "origin/$GITHUB_REF_NAME" ^"origin/master" | wc -l)
          VERSION=$(echo ${{github.ref}} | tail -c +20)-preview.$PREVIEW_VERSION
          echo $VERSION
          npm version --no-git-tag-version --allow-same-version $VERSION
          npm set //npm.pkg.github.com/:_authToken ${{ secrets.GITHUB_TOKEN }}
          npm set registry https://npm.pkg.github.com/mermaid-js
          json -I -f package.json -e 'this.name="@mermaid-js/mermaid"' # Package name needs to be set to a scoped one because GitHub registry requires this
          json -I -f package.json -e 'this.repository="git://github.com/mermaid-js/mermaid"' # Repo url needs to have a specific format too
          npm publish
scorecard perms security .github/workflows/scorecard.yml
Triggers
branch_protection_rule, push, schedule
Runs on
ubuntu-latest
Jobs
analysis
Actions
ossf/scorecard-action, github/codeql-action/upload-sarif
View raw YAML
name: Scorecard supply-chain security
on:
  branch_protection_rule:
  push:
    branches:
      - develop
  schedule:
    - cron: 29 15 * * 0
permissions: read-all
jobs:
  analysis:
    name: Scorecard analysis
    permissions:
      id-token: write
      security-events: write
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
        with:
          persist-credentials: false
      - name: Run analysis
        uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
        with:
          results_file: results.sarif
          results_format: sarif
          publish_results: true
      - name: Upload artifact
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: SARIF file
          path: results.sarif
          retention-days: 5
      - name: Upload to code-scanning
        uses: github/codeql-action/upload-sarif@5378192d256ef1302a6980fffe5ca04426d43091 # v3.28.21
        with:
          sarif_file: results.sarif
test perms .github/workflows/test.yml
Triggers
push, pull_request, merge_group
Runs on
ubuntu-latest
Jobs
unit-test
Actions
pnpm/action-setup, codecov/codecov-action
Commands
  • pnpm install --frozen-lockfile
  • pnpm test:coverage
  • pnpm exec vitest run ./packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts --coverage
  • pnpm test:check:tsc
View raw YAML
name: Unit Tests

on: [push, pull_request, merge_group]

permissions:
  contents: read

jobs:
  unit-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

      - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
        # uses version from "packageManager" field in package.json

      - name: Setup Node.js
        uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
        with:
          cache: pnpm
          node-version-file: '.node-version'

      - name: Install Packages
        run: |
          pnpm install --frozen-lockfile
        env:
          CYPRESS_CACHE_FOLDER: .cache/Cypress

      - name: Run Unit Tests
        run: |
          pnpm test:coverage

      - name: Run ganttDb tests using California timezone
        env:
          # Makes sure that gantt db works even in a timezone that has daylight savings
          # since some days have 25 hours instead of 24.
          TZ: America/Los_Angeles
        run: |
          pnpm exec vitest run ./packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts --coverage

      - name: Verify out-of-tree build with TypeScript
        run: |
          pnpm test:check:tsc

      - name: Upload Coverage to Codecov
        uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5.3.1
        # Run step only pushes to develop and pull_requests
        if: ${{ github.event_name == 'pull_request' || github.ref == 'refs/heads/develop' }}
        with:
          files: ./coverage/vitest/lcov.info
          flags: unit
          name: mermaid-codecov
          fail_ci_if_error: false
          verbose: true
          token: 6845cc80-77ee-4e17-85a1-026cd95e0766
unlock-reopened-issues .github/workflows/unlock-reopened-issues.yml
Triggers
issues
Runs on
ubuntu-latest
Jobs
triage
Actions
Dunning-Kruger/unlock-issues
View raw YAML
name: Unlock reopened issue

on:
  issues:
    types: [reopened]

jobs:
  triage:
    runs-on: ubuntu-latest
    steps:
      - uses: Dunning-Kruger/unlock-issues@b06b7f7e5c3f2eaa1c6d5d89f40930e4d6d9699e # v1
        with:
          repo-token: '${{ secrets.GITHUB_TOKEN }}'
update-browserlist .github/workflows/update-browserlist.yml
Triggers
schedule, workflow_dispatch
Runs on
ubuntu-latest
Jobs
update-browser-list
Actions
pnpm/action-setup, EndBug/add-and-commit, peter-evans/create-pull-request
Commands
  • npx update-browserslist-db@latest
View raw YAML
name: Update Browserslist
on:
  schedule:
    - cron: '0 7 * * 1'
  workflow_dispatch:

jobs:
  update-browser-list:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
      - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
      - run: npx update-browserslist-db@latest
      - name: Commit changes
        uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9.1.4
        with:
          author_name: ${{ github.actor }}
          author_email: ${{ github.actor }}@users.noreply.github.com
          message: 'chore: update browsers list'
          push: false
      - name: Create Pull Request
        uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9
        with:
          branch: update-browserslist
          title: Update Browserslist
validate-lockfile .github/workflows/validate-lockfile.yml
Triggers
pull_request_target
Runs on
ubuntu-latest
Jobs
validate-lockfile
Actions
peter-evans/find-comment, peter-evans/create-or-update-comment
Commands
  • issues=() # 1) No tarball references if grep -qF 'tarball:' pnpm-lock.yaml; then issues+=("• Tarball references found (forbidden)") fi # 2) No unwanted vitepress paths if grep -qF 'packages/mermaid/src/vitepress' pnpm-lock.yaml; then issues+=("• Disallowed path 'packages/mermaid/src/vitepress' present. Run \`rm -rf packages/mermaid/src/vitepress && pnpm install\` to regenerate.") fi # 3) Lockfile only changes when package.json changes git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} > changed.txt if grep -q '^pnpm-lock.yaml$' changed.txt && ! grep -q 'package.json' changed.txt; then issues+=("• pnpm-lock.yaml changed without any package.json modification") fi # If any issues, output them and fail if [ ${#issues[@]} -gt 0 ]; then # Use the new GITHUB_OUTPUT approach to set a multiline output { echo "errors<<EOF" printf '%s\n' "${issues[@]}" echo "EOF" } >> $GITHUB_OUTPUT exit 1 fi
View raw YAML
name: Validate pnpm-lock.yaml

on:
  pull_request_target:
    paths:
      - 'pnpm-lock.yaml'
      - '**/package.json'
      - '.github/workflows/validate-lockfile.yml'

jobs:
  validate-lockfile:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
          ref: ${{ github.event.pull_request.head.sha }}
          repository: ${{ github.event.pull_request.head.repo.full_name }}

      - name: Validate pnpm-lock.yaml entries
        id: validate # give this step an ID so we can reference its outputs
        run: |
          issues=()

          # 1) No tarball references
          if grep -qF 'tarball:' pnpm-lock.yaml; then
            issues+=("• Tarball references found (forbidden)")
          fi

          # 2) No unwanted vitepress paths
          if grep -qF 'packages/mermaid/src/vitepress' pnpm-lock.yaml; then
            issues+=("• Disallowed path 'packages/mermaid/src/vitepress' present. Run \`rm -rf packages/mermaid/src/vitepress && pnpm install\` to regenerate.")
          fi

          # 3) Lockfile only changes when package.json changes
          git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} > changed.txt
          if grep -q '^pnpm-lock.yaml$' changed.txt && ! grep -q 'package.json' changed.txt; then
            issues+=("• pnpm-lock.yaml changed without any package.json modification")
          fi

          # If any issues, output them and fail
          if [ ${#issues[@]} -gt 0 ]; then
            # Use the new GITHUB_OUTPUT approach to set a multiline output
            {
              echo "errors<<EOF"
              printf '%s\n' "${issues[@]}"
              echo "EOF"
            } >> $GITHUB_OUTPUT
            exit 1
          fi

      - name: Find existing lockfile validation comment
        if: always()
        uses: peter-evans/find-comment@v3
        id: find-comment
        with:
          issue-number: ${{ github.event.pull_request.number }}
          comment-author: 'github-actions[bot]'
          body-includes: 'Lockfile Validation Failed'

      - name: Comment on PR if validation failed
        if: failure()
        uses: peter-evans/create-or-update-comment@v5
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          issue-number: ${{ github.event.pull_request.number }}
          comment-id: ${{ steps.find-comment.outputs.comment-id }}
          edit-mode: replace
          body: |
            ❌ **Lockfile Validation Failed**

            The following issue(s) were detected:
            ${{ steps.validate.outputs.errors }}

            Please address these and push an update.

            _Posted automatically by GitHub Actions_

      - name: Delete comment if validation passed
        if: success() && steps.find-comment.outputs.comment-id != ''
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            await github.rest.issues.deleteComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              comment_id: ${{ steps.find-comment.outputs.comment-id }},
            });