diff --git a/.github/workflows/benchmark-comment.yml b/.github/workflows/benchmark-comment.yml
new file mode 100644
index 0000000..7ef7120
--- /dev/null
+++ b/.github/workflows/benchmark-comment.yml
@@ -0,0 +1,248 @@
+name: Benchmark Comment
+
+on:
+ workflow_run:
+ workflows: [Benchmark]
+ types: [completed]
+
+# Artifact parsing and comment publication intentionally use separate tokens.
+# Neither job checks out or executes code from the triggering pull request.
+permissions: {}
+
+concurrency:
+ group: benchmark-comment-${{ github.event.workflow_run.head_sha }}
+ cancel-in-progress: true
+
+jobs:
+ prepare:
+ if: >-
+ github.event.workflow_run.conclusion == 'success' &&
+ github.event.workflow_run.event == 'pull_request'
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ permissions:
+ actions: read
+ outputs:
+ report: ${{ steps.report.outputs.report }}
+
+ steps:
+ - name: Download Bun benchmark artifact
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ run-id: ${{ github.event.workflow_run.id }}
+ name: benchmark-results-bun-latest
+ path: ${{ runner.temp }}/benchmark-results
+
+ - name: Download Node.js 22 benchmark artifact
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ run-id: ${{ github.event.workflow_run.id }}
+ name: benchmark-results-node-22
+ path: ${{ runner.temp }}/benchmark-results
+
+ - name: Download Node.js 24 benchmark artifact
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ run-id: ${{ github.event.workflow_run.id }}
+ name: benchmark-results-node-24
+ path: ${{ runner.temp }}/benchmark-results
+
+ - name: Download Node.js 25 benchmark artifact
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ run-id: ${{ github.event.workflow_run.id }}
+ name: benchmark-results-node-25
+ path: ${{ runner.temp }}/benchmark-results
+
+ - name: Sanitize benchmark report
+ id: report
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
+ env:
+ BENCHMARK_RESULTS: ${{ runner.temp }}/benchmark-results
+ with:
+ script: |
+ const fs = require('fs')
+ const path = require('path')
+
+ const root = fs.realpathSync(process.env.BENCHMARK_RESULTS)
+ const expectedRuntimes = ['bun-latest', 'node-22', 'node-24', 'node-25']
+
+ const truncateUtf8 = (value, maxBytes) => {
+ const bytes = Buffer.from(value, 'utf8')
+ if (bytes.length <= maxBytes) return value
+
+ let end = maxBytes
+ while (end > 0 && (bytes[end] & 0xc0) === 0x80) end--
+ return bytes.subarray(0, end).toString('utf8')
+ }
+ const sanitize = value =>
+ truncateUtf8(value, 13000)
+ .replace(//g, '')
+ .replace(/<[^>]*>/g, '')
+ .replaceAll('<', '<')
+ .replaceAll('>', '>')
+ .replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/g, '')
+ .replace(/[\u202a-\u202e\u2066-\u2069]/g, '')
+ .replaceAll('@', '@\u200b')
+ .replaceAll('](', ']\u200b(')
+ .replace(/https?:\/\//gi, match => match.replace('://', ':\u200b//'))
+
+ const sections = expectedRuntimes.map(runtime => {
+ const name = `final-comparison-${runtime}.md`
+ const file = path.join(root, name)
+ const metadata = fs.lstatSync(file)
+ if (!metadata.isFile() || metadata.size > 50000) {
+ throw new Error(`Invalid benchmark comparison artifact: ${name}`)
+ }
+ if (path.dirname(fs.realpathSync(file)) !== root) {
+ throw new Error(`Benchmark artifact escapes its directory: ${name}`)
+ }
+ return sanitize(fs.readFileSync(file, 'utf8'))
+ })
+
+ const report = [
+ '> The tables below were generated by pull-request-controlled benchmark code.',
+ '> Links, HTML, mentions, control characters, and bidirectional controls were sanitized.',
+ '',
+ ...sections
+ ].join('\n')
+ if (Buffer.byteLength(report, 'utf8') > 56000) {
+ throw new Error('Sanitized benchmark report exceeds the size limit')
+ }
+
+ core.setOutput('report', Buffer.from(report, 'utf8').toString('base64'))
+
+ publish:
+ needs: prepare
+ if: needs.prepare.result == 'success' && needs.prepare.outputs.report != ''
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ permissions:
+ issues: write
+ pull-requests: read
+
+ steps:
+ - name: Create or update benchmark comment
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
+ env:
+ BENCHMARK_REPORT: ${{ needs.prepare.outputs.report }}
+ with:
+ script: |
+ const marker = ''
+ const runIdMarker = '`,
+ '# đ Benchmark Results',
+ '',
+ `Benchmark report for PR head [\`${shortHead}\`](${commitUrl}) from [workflow run ${run.id}](${run.html_url}).`,
+ '',
+ report,
+ '',
+ '',
+ 'âšī¸ Benchmark Details
',
+ '',
+ `- **Base branch**: \`${pullRequest.base.ref}\``,
+ `- **PR head**: [\`${shortHead}\`](${commitUrl})`,
+ `- **Workflow run**: [${run.id}](${run.html_url})`,
+ `- **Completed**: ${run.updated_at}`,
+ ' '
+ ].join('\n').slice(0, 60000)
+
+ const comments = await github.paginate(github.rest.issues.listComments, {
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: issueNumber,
+ per_page: 100
+ })
+ const previous = comments.find(comment =>
+ comment.user?.login === 'github-actions[bot]' && comment.body?.startsWith(marker)
+ )
+
+ if (previous) {
+ const previousRunId = Number(
+ previous.body?.match(//)?.[1] ?? 0
+ )
+ if (previousRunId > run.id) {
+ core.info(`Skipping older workflow run ${run.id}`)
+ return
+ }
+ await github.rest.issues.updateComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ comment_id: previous.id,
+ body
+ })
+ } else {
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: issueNumber,
+ body
+ })
+ }
diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml
index ca43d60..1a72a84 100644
--- a/.github/workflows/benchmark.yml
+++ b/.github/workflows/benchmark.yml
@@ -2,67 +2,104 @@ name: Benchmark
on:
pull_request:
- branches: [ main, master ]
+ branches: [main, master]
+
+# Pull-request code and benchmark dependencies are untrusted. Keep this workflow
+# read-only; the trusted Benchmark Comment workflow publishes the artifacts.
+permissions:
+ contents: read
+
+concurrency:
+ group: benchmark-${{ github.event.pull_request.number }}
+ cancel-in-progress: true
jobs:
benchmark:
runs-on: namespace-profile-default
+ timeout-minutes: 40
# Only run PR comparison for pull requests
if: github.event_name == 'pull_request'
strategy:
+ fail-fast: false
matrix:
runtime: [node-22, node-24, node-25, bun-latest]
steps:
- - name: Checkout repository
- uses: actions/checkout@v4
- with:
- fetch-depth: 0 # Need full history to find merge-base
-
- - name: Parse runtime version
- id: version
- run: echo "value=${RUNTIME#*-}" >> $GITHUB_OUTPUT
- env:
- RUNTIME: ${{ matrix.runtime }}
-
- - name: Setup Node.js (node runtime)
- if: startsWith(matrix.runtime, 'node-')
- uses: actions/setup-node@v4
- with:
- node-version: ${{ steps.version.outputs.value }}
- cache: 'npm'
-
- - name: Setup Node.js (bun runtime)
- if: startsWith(matrix.runtime, 'bun-')
- uses: actions/setup-node@v4
- with:
- node-version: 22
- cache: 'npm'
-
- - name: Setup Bun
- if: startsWith(matrix.runtime, 'bun-')
- uses: oven-sh/setup-bun@v2
- with:
- bun-version: ${{ steps.version.outputs.value }}
-
- - name: Find merge base
- id: merge-base
- run: |
- git fetch origin ${{ github.base_ref }}
- MERGE_BASE=$(git merge-base HEAD origin/${{ github.base_ref }})
- echo "commit=$MERGE_BASE" >> $GITHUB_OUTPUT
- echo "Merge base: $MERGE_BASE"
-
- - name: Benchmark baseline (merge-base)
- run: |
- echo "Benchmarking merge-base: ${{ steps.merge-base.outputs.commit }}"
- git checkout ${{ steps.merge-base.outputs.commit }}
- npm ci
-
- # Check if benchmark:json script exists in package.json
- if [ -f package.json ] && grep -q '"benchmark:json"' package.json; then
- echo "Found benchmark:json script, running benchmarks..."
+ - name: Checkout repository
+ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ with:
+ fetch-depth: 0 # Need full history to find merge-base
+ persist-credentials: false
+
+ - name: Parse runtime version
+ id: version
+ run: echo "value=${RUNTIME#*-}" >> $GITHUB_OUTPUT
+ env:
+ RUNTIME: ${{ matrix.runtime }}
+
+ - name: Setup Node.js (node runtime)
+ if: startsWith(matrix.runtime, 'node-')
+ uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
+ with:
+ node-version: ${{ steps.version.outputs.value }}
+ cache: 'npm'
+
+ - name: Setup Node.js (bun runtime)
+ if: startsWith(matrix.runtime, 'bun-')
+ uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
+ with:
+ node-version: 22
+ cache: 'npm'
+
+ - name: Setup Bun
+ if: startsWith(matrix.runtime, 'bun-')
+ uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
+ with:
+ bun-version: ${{ steps.version.outputs.value }}
+
+ - name: Find merge base
+ id: merge-base
+ run: |
+ git fetch origin ${{ github.base_ref }}
+ MERGE_BASE=$(git merge-base HEAD origin/${{ github.base_ref }})
+ echo "commit=$MERGE_BASE" >> $GITHUB_OUTPUT
+ echo "Merge base: $MERGE_BASE"
+
+ - name: Benchmark baseline (merge-base)
+ run: |
+ echo "Benchmarking merge-base: ${{ steps.merge-base.outputs.commit }}"
+ git checkout ${{ steps.merge-base.outputs.commit }}
+ npm ci
+
+ # Check if benchmark:json script exists in package.json
+ if [ -f package.json ] && grep -q '"benchmark:json"' package.json; then
+ echo "Found benchmark:json script, running benchmarks..."
+ # Use appropriate runtime for benchmark
+ if [[ "${{ matrix.runtime }}" == bun-* ]]; then
+ BENCHMARK_CMD="bun run benchmark:json"
+ else
+ BENCHMARK_CMD="npm run benchmark:json"
+ fi
+
+ if timeout 900s $BENCHMARK_CMD baseline-results-${{ matrix.runtime }}.json 2>baseline-error-${{ matrix.runtime }}.log; then
+ echo "Baseline benchmark completed successfully"
+ else
+ echo "Baseline benchmark failed or timed out"
+ echo '[]' > baseline-results-${{ matrix.runtime }}.json
+ fi
+ else
+ echo "benchmark:json script not found in merge-base commit, skipping baseline"
+ echo "Merge-base does not have benchmark:json script" > baseline-error-${{ matrix.runtime }}.log
+ echo '[]' > baseline-results-${{ matrix.runtime }}.json
+ fi
+
+ - name: Benchmark PR branch
+ run: |
+ echo "Benchmarking PR: ${{ github.sha }}"
+ git checkout ${{ github.sha }}
+ npm ci
+
# Use appropriate runtime for benchmark
if [[ "${{ matrix.runtime }}" == bun-* ]]; then
BENCHMARK_CMD="bun run benchmark:json"
@@ -70,159 +107,35 @@ jobs:
BENCHMARK_CMD="npm run benchmark:json"
fi
- if timeout 900s $BENCHMARK_CMD baseline-results-${{ matrix.runtime }}.json 2>baseline-error-${{ matrix.runtime }}.log; then
- echo "Baseline benchmark completed successfully"
+ if timeout 900s $BENCHMARK_CMD pr-results-${{ matrix.runtime }}.json 2>pr-error-${{ matrix.runtime }}.log; then
+ echo "PR benchmark completed successfully"
else
- echo "Baseline benchmark failed or timed out"
- echo '[]' > baseline-results-${{ matrix.runtime }}.json
- fi
- else
- echo "benchmark:json script not found in merge-base commit, skipping baseline"
- echo "Merge-base does not have benchmark:json script" > baseline-error-${{ matrix.runtime }}.log
- echo '[]' > baseline-results-${{ matrix.runtime }}.json
- fi
-
- - name: Benchmark PR branch
- run: |
- echo "Benchmarking PR: ${{ github.sha }}"
- git checkout ${{ github.sha }}
- npm ci
-
- # Use appropriate runtime for benchmark
- if [[ "${{ matrix.runtime }}" == bun-* ]]; then
- BENCHMARK_CMD="bun run benchmark:json"
- else
- BENCHMARK_CMD="npm run benchmark:json"
- fi
-
- if timeout 900s $BENCHMARK_CMD pr-results-${{ matrix.runtime }}.json 2>pr-error-${{ matrix.runtime }}.log; then
- echo "PR benchmark completed successfully"
- else
- echo "PR benchmark failed or timed out"
- echo '[]' > pr-results-${{ matrix.runtime }}.json
- fi
-
- - name: Compare results
- id: comparison
- run: |
- # Generate comparison markdown (using Node.js since it's not performance-critical)
- npm run compare-benchmarks baseline-results-${{ matrix.runtime }}.json pr-results-${{ matrix.runtime }}.json > comparison-${{ matrix.runtime }}.md
-
- # Add runtime version header
- echo "### ${{ matrix.runtime }}" > final-comparison-${{ matrix.runtime }}.md
- echo "" >> final-comparison-${{ matrix.runtime }}.md
- cat comparison-${{ matrix.runtime }}.md >> final-comparison-${{ matrix.runtime }}.md
- echo "" >> final-comparison-${{ matrix.runtime }}.md
-
- - name: Upload results as artifacts
- uses: actions/upload-artifact@v4
- with:
- name: benchmark-results-${{ matrix.runtime }}
- path: |
- baseline-results-${{ matrix.runtime }}.json
- pr-results-${{ matrix.runtime }}.json
- baseline-error-${{ matrix.runtime }}.log
- pr-error-${{ matrix.runtime }}.log
- final-comparison-${{ matrix.runtime }}.md
-
- # Job to collect all runtime results and post PR comment
- comment:
- runs-on: ubuntu-latest
- needs: benchmark
- if: github.event_name == 'pull_request'
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
- with:
- fetch-depth: 0 # Need full history to find merge-base
-
- - name: Find merge base
- id: merge-base
- run: |
- git fetch origin ${{ github.base_ref }}
- MERGE_BASE=$(git merge-base HEAD origin/${{ github.base_ref }})
- echo "commit=$MERGE_BASE" >> $GITHUB_OUTPUT
- echo "short-commit=${MERGE_BASE:0:7}" >> $GITHUB_OUTPUT
- echo "Merge base: $MERGE_BASE"
-
- - name: Download all benchmark results
- uses: actions/download-artifact@v4
- with:
- path: benchmark-results
-
- - name: Combine results and post comment
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
- # Combine all runtime results (Node.js and Bun)
- echo "" > combined-results.md
- echo "# đ Benchmark Results" >> combined-results.md
- echo "" >> combined-results.md
- echo "Performance comparison against merge-base [\`${{ steps.merge-base.outputs.short-commit }}\`](${{ github.server_url }}/${{ github.repository }}/commit/${{ steps.merge-base.outputs.commit }})" >> combined-results.md
- echo "" >> combined-results.md
-
- # Add results for each runtime (iterate over actual artifacts)
- for result_dir in benchmark-results/benchmark-results-*; do
- if [ -d "$result_dir" ]; then
- # Extract runtime from directory name (benchmark-results-node-22 -> node-22)
- runtime=$(basename "$result_dir" | sed 's/benchmark-results-//')
-
- if [ -f "$result_dir/final-comparison-${runtime}.md" ]; then
- cat "$result_dir/final-comparison-${runtime}.md" >> combined-results.md
- else
- echo "### ${runtime}" >> combined-results.md
- echo "â ī¸ Benchmark failed or timed out" >> combined-results.md
- echo "" >> combined-results.md
-
- # Add error details if available
- if [ -f "$result_dir/baseline-error-${runtime}.log" ]; then
- echo "" >> combined-results.md
- echo "Baseline Error Log
" >> combined-results.md
- echo "" >> combined-results.md
- echo "\`\`\`" >> combined-results.md
- head -20 "$result_dir/baseline-error-${runtime}.log" >> combined-results.md
- echo "\`\`\`" >> combined-results.md
- echo " " >> combined-results.md
- echo "" >> combined-results.md
- fi
-
- if [ -f "$result_dir/pr-error-${runtime}.log" ]; then
- echo "" >> combined-results.md
- echo "PR Error Log
" >> combined-results.md
- echo "" >> combined-results.md
- echo "\`\`\`" >> combined-results.md
- head -20 "$result_dir/pr-error-${runtime}.log" >> combined-results.md
- echo "\`\`\`" >> combined-results.md
- echo " " >> combined-results.md
- echo "" >> combined-results.md
- fi
- fi
+ echo "PR benchmark failed or timed out"
+ echo '[]' > pr-results-${{ matrix.runtime }}.json
fi
- done
-
- echo "" >> combined-results.md
- echo "" >> combined-results.md
- echo "âšī¸ Benchmark Details
" >> combined-results.md
- echo "" >> combined-results.md
- echo "- **Baseline**: Merge-base commit [\`${{ steps.merge-base.outputs.short-commit }}\`](${{ github.server_url }}/${{ github.repository }}/commit/${{ steps.merge-base.outputs.commit }}) where this PR branched from ${{ github.base_ref }}" >> combined-results.md
- echo "- **Comparison**: Current PR head [\`${GITHUB_SHA:0:7}\`](${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }})" >> combined-results.md
- echo "- **Timestamp**: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" >> combined-results.md
- echo " " >> combined-results.md
-
- # Find existing comment or create new one
- COMMENT_ID=$(gh api "repos/${{ github.repository }}/issues/${{ github.event.number }}/comments" \
- --jq '.[] | select(.body | contains("")) | .id' | head -1)
-
- if [ -n "$COMMENT_ID" ]; then
- echo "Updating existing comment: $COMMENT_ID"
- gh api "repos/${{ github.repository }}/issues/comments/$COMMENT_ID" \
- -X PATCH \
- -f body="$(cat combined-results.md)"
- else
- echo "Creating new comment"
- gh api "repos/${{ github.repository }}/issues/${{ github.event.number }}/comments" \
- -X POST \
- -f body="$(cat combined-results.md)"
- fi
+ - name: Compare results
+ id: comparison
+ run: |
+ # Generate comparison markdown (using Node.js since it's not performance-critical)
+ npm run compare-benchmarks baseline-results-${{ matrix.runtime }}.json pr-results-${{ matrix.runtime }}.json > comparison-${{ matrix.runtime }}.md
+
+ # Add runtime version header
+ echo "### ${{ matrix.runtime }}" > final-comparison-${{ matrix.runtime }}.md
+ echo "" >> final-comparison-${{ matrix.runtime }}.md
+ cat comparison-${{ matrix.runtime }}.md >> final-comparison-${{ matrix.runtime }}.md
+ echo "" >> final-comparison-${{ matrix.runtime }}.md
+
+ - name: Upload results as artifacts
+ if: always()
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
+ with:
+ name: benchmark-results-${{ matrix.runtime }}
+ path: |
+ baseline-results-${{ matrix.runtime }}.json
+ pr-results-${{ matrix.runtime }}.json
+ baseline-error-${{ matrix.runtime }}.log
+ pr-error-${{ matrix.runtime }}.log
+ final-comparison-${{ matrix.runtime }}.md
+ if-no-files-found: error
+ retention-days: 14
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index c0d4adf..dfb1f1b 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -2,27 +2,32 @@ name: Build
on:
push:
- branches: [ main, master ]
+ branches: [main, master]
pull_request:
- branches: [ main, master ]
+ branches: [main, master]
workflow_call:
+permissions:
+ contents: read
+
jobs:
build:
runs-on: ubuntu-latest
steps:
- - name: Checkout repository
- uses: actions/checkout@v4
+ - name: Checkout repository
+ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ with:
+ persist-credentials: false
- - name: Setup Node.js
- uses: actions/setup-node@v4
- with:
- node-version: 25
- cache: 'npm'
+ - name: Setup Node.js
+ uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
+ with:
+ node-version: 25
+ cache: 'npm'
- - name: Install dependencies
- run: npm ci
+ - name: Install dependencies
+ run: npm ci
- - name: Build project
- run: npm run build
\ No newline at end of file
+ - name: Build project
+ run: npm run build
diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml
index 1d897a3..cd059d7 100644
--- a/.github/workflows/commitlint.yml
+++ b/.github/workflows/commitlint.yml
@@ -2,15 +2,21 @@ name: Commitlint
on:
pull_request:
- branches: [ main, master ]
+ branches: [main, master]
+
+permissions:
+ contents: read
+ pull-requests: read
jobs:
commitlint:
runs-on: ubuntu-latest
steps:
- - name: Checkout repository
- uses: actions/checkout@v4
+ - name: Checkout repository
+ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ with:
+ persist-credentials: false
- - name: Lint commit messages
- uses: wagoid/commitlint-github-action@v6
\ No newline at end of file
+ - name: Lint commit messages
+ uses: wagoid/commitlint-github-action@b948419dd99f3fd78a6548d48f94e3df7f6bf3ed # v6
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index fb1100f..54b82ba 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -2,30 +2,35 @@ name: Lint
on:
push:
- branches: [ main, master ]
+ branches: [main, master]
pull_request:
- branches: [ main, master ]
+ branches: [main, master]
workflow_call:
+permissions:
+ contents: read
+
jobs:
lint:
runs-on: ubuntu-latest
steps:
- - name: Checkout repository
- uses: actions/checkout@v4
+ - name: Checkout repository
+ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ with:
+ persist-credentials: false
- - name: Setup Node.js
- uses: actions/setup-node@v4
- with:
- node-version: 25
- cache: 'npm'
+ - name: Setup Node.js
+ uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
+ with:
+ node-version: 25
+ cache: 'npm'
- - name: Install dependencies
- run: npm ci
+ - name: Install dependencies
+ run: npm ci
- - name: Run linting
- run: npm run lint
+ - name: Run linting
+ run: npm run lint
- - name: Check formatting
- run: npm run format:check
\ No newline at end of file
+ - name: Check formatting
+ run: npm run format:check
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 7b0128e..74d9ee0 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -2,11 +2,14 @@ name: Test
on:
push:
- branches: [ main, master ]
+ branches: [main, master]
pull_request:
- branches: [ main, master ]
+ branches: [main, master]
workflow_call:
+permissions:
+ contents: read
+
jobs:
test:
runs-on: ubuntu-latest
@@ -16,54 +19,56 @@ jobs:
runtime: [node-22, node-24, node-25, bun-latest]
steps:
- - name: Checkout repository
- uses: actions/checkout@v4
+ - name: Checkout repository
+ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+ with:
+ persist-credentials: false
- - name: Parse runtime version
- id: version
- run: echo "value=${RUNTIME#*-}" >> $GITHUB_OUTPUT
- env:
- RUNTIME: ${{ matrix.runtime }}
+ - name: Parse runtime version
+ id: version
+ run: echo "value=${RUNTIME#*-}" >> $GITHUB_OUTPUT
+ env:
+ RUNTIME: ${{ matrix.runtime }}
- - name: Setup Node.js (node runtime)
- if: startsWith(matrix.runtime, 'node-')
- uses: actions/setup-node@v4
- with:
- node-version: ${{ steps.version.outputs.value }}
- cache: 'npm'
+ - name: Setup Node.js (node runtime)
+ if: startsWith(matrix.runtime, 'node-')
+ uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
+ with:
+ node-version: ${{ steps.version.outputs.value }}
+ cache: 'npm'
- - name: Setup Node.js (bun runtime)
- if: startsWith(matrix.runtime, 'bun-')
- uses: actions/setup-node@v4
- with:
- node-version: 22
- cache: 'npm'
+ - name: Setup Node.js (bun runtime)
+ if: startsWith(matrix.runtime, 'bun-')
+ uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
+ with:
+ node-version: 22
+ cache: 'npm'
- - name: Setup Bun
- if: startsWith(matrix.runtime, 'bun-')
- uses: oven-sh/setup-bun@v2
- with:
- bun-version: ${{ steps.version.outputs.value }}
+ - name: Setup Bun
+ if: startsWith(matrix.runtime, 'bun-')
+ uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
+ with:
+ bun-version: ${{ steps.version.outputs.value }}
- - name: Install dependencies
- run: npm ci
+ - name: Install dependencies
+ run: npm ci
- - name: Run tests with coverage (Node.js)
- if: startsWith(matrix.runtime, 'node-')
- run: npm run cover
+ - name: Run tests with coverage (Node.js)
+ if: startsWith(matrix.runtime, 'node-')
+ run: npm run cover
- - name: Run tests with coverage (Bun)
- if: startsWith(matrix.runtime, 'bun-')
- run: bun run cover
+ - name: Run tests with coverage (Bun)
+ if: startsWith(matrix.runtime, 'bun-')
+ run: bun run cover
- - name: Display coverage summary
- run: npx nyc report --reporter=text-summary
+ - name: Display coverage summary
+ run: npx nyc report --reporter=text-summary
- - name: Upload coverage to artifacts
- uses: actions/upload-artifact@v4
- if: matrix.runtime == 'node-22'
- with:
- name: coverage-report
- path: |
- coverage.lcov
- .nyc_output/
\ No newline at end of file
+ - name: Upload coverage to artifacts
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
+ if: matrix.runtime == 'node-22'
+ with:
+ name: coverage-report
+ path: |
+ coverage.lcov
+ .nyc_output/