Skip to content

perf(component): render initial diff during SSR #144

perf(component): render initial diff during SSR

perf(component): render initial diff during SSR #144

Workflow file for this run

# 1. Workflow metadata
name: Publish to NPM
permissions:
contents: read
packages: read
pull-requests: read
# 2. Trigger conditions
on:
pull_request:
types: [closed]
branches:
- main
- '!dependabot/**'
paths:
- src/**
- '!docs/**'
- '!tests/**'
- '!src/lib/test/**'
- '!src/routes/**'
- package.json
- pnpm-lock.yaml
- .github/workflows/npm-publish.yml
workflow_dispatch:
inputs:
# trunk-ignore(checkov/CKV_GHA_7): We need manual version control for releases
version_bump:
description: Version bump type (major/minor/patch) or skip
required: true
type: choice
options:
- skip
- patch
- minor
- major
custom_message:
description: Release message
required: false
type: string
jobs:
check-if-merged:
runs-on: blacksmith-2vcpu-ubuntu-2404 # trunk-ignore(actionlint/runner-label)
outputs:
should_run: ${{ steps.check.outputs.should_run }}
has_skip_label: ${{ steps.check.outputs.has_skip_label }}
steps:
- id: check
env:
EVENT_NAME: ${{ github.event_name }}
PR_MERGED: ${{ github.event.pull_request.merged }}
HAS_SKIP_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'skip-publish') }}
run: |
if [[ "$EVENT_NAME" == "pull_request" && "$PR_MERGED" != "true" ]]; then
echo "should_run=false" >> $GITHUB_OUTPUT
else
echo "should_run=true" >> $GITHUB_OUTPUT
fi
echo "has_skip_label=$HAS_SKIP_LABEL" >> $GITHUB_OUTPUT
debug-check:
needs: check-if-merged
if: needs.check-if-merged.outputs.should_run == 'true'
runs-on: blacksmith-2vcpu-ubuntu-2404 # trunk-ignore(actionlint/runner-label)
environment: ci
steps:
- name: Checkout
uses: actions/checkout@v7 # zizmor: ignore[unpinned-uses]
with:
persist-credentials: false
token: ${{ secrets.ACTIONS_KEY }}
- name: Check for debugging statements
run: |
# Function to check file content
check_file() {
local file="$1"
local has_debug=false
local debug_output=""
# Check for $inspect (not in comments)
if grep -n -P '^(?!\s*//|\s*/\*|\s*\*).*\$inspect' "$file" > /dev/null; then
match=$(grep -n -P '^(?!\s*//|\s*/\*|\s*\*).*\$inspect.*' "$file" | head -n 1)
line_num=$(echo "$match" | cut -d: -f1)
statement=$(echo "$match" | cut -d: -f2-)
echo "::error file=$file::Found \$inspect statement on line $line_num: $statement"
has_debug=true
fi
# Check for console.log (not in comments)
if grep -n -P '^(?!\s*//|\s*/\*|\s*\*).*console\.log' "$file" > /dev/null; then
match=$(grep -n -P '^(?!\s*//|\s*/\*|\s*\*).*console\.log.*' "$file" | head -n 1)
line_num=$(echo "$match" | cut -d: -f1)
statement=$(echo "$match" | cut -d: -f2-)
echo "::error file=$file::Found console.log statement on line $line_num: $statement"
has_debug=true
fi
# Check for console.debug (not in comments)
if grep -n -P '^(?!\s*//|\s*/\*|\s*\*).*console\.debug' "$file" > /dev/null; then
match=$(grep -n -P '^(?!\s*//|\s*/\*|\s*\*).*console\.debug.*' "$file" | head -n 1)
line_num=$(echo "$match" | cut -d: -f1)
statement=$(echo "$match" | cut -d: -f2-)
echo "::error file=$file::Found console.debug statement on line $line_num: $statement"
has_debug=true
fi
# Check for debugger statements (not in comments)
if grep -n -P '^(?!\s*//|\s*/\*|\s*\*).*debugger;' "$file" > /dev/null; then
match=$(grep -n -P '^(?!\s*//|\s*/\*|\s*\*).*debugger;' "$file" | head -n 1)
line_num=$(echo "$match" | cut -d: -f1)
statement=$(echo "$match" | cut -d: -f2-)
echo "::error file=$file::Found debugger statement on line $line_num: $statement"
has_debug=true
fi
$has_debug && return 1 || return 0
}
# Set up file descriptor for output capture
exec 5>&1
# Run checks and capture output
OUTPUT=$(
{
has_errors=0
# Check Svelte files
while IFS= read -r file; do
if ! check_file "$file"; then
has_errors=1
fi
done < <(find src/lib -type f -name "*.svelte" ! -path "*/test/*")
# Check TypeScript files (excluding log.ts which intentionally uses console.log)
while IFS= read -r file; do
if ! check_file "$file"; then
has_errors=1
fi
done < <(find src/lib -type f -name "*.ts" ! -name "*.test.ts" ! -path "*/test/*" ! -path "src/lib/utils/log.ts")
exit $has_errors
} | tee >(cat >&5)
)
# Store output for the next step
{
echo "DEBUG_OUTPUT<<EOF"
echo "$OUTPUT"
echo "EOF"
} >> $GITHUB_ENV
# Exit with the original error code
[[ $OUTPUT == *"::error"* ]] && exit 1 || exit 0
- name: Upload Results
if: failure() && github.event_name == 'pull_request'
uses: actions/github-script@v9 # zizmor: ignore[unpinned-uses]
with:
github-token: ${{ secrets.ACTIONS_KEY }}
script: |
const fs = require('fs');
const { owner, repo } = context.repo;
// Get the debug check output
const debugOutput = process.env.DEBUG_OUTPUT || '';
let commentBody = '❌ Debug statements check failed.\n\nFound debugging statements in the following locations:\n\n';
// Add each error location to the comment
debugOutput.split('\n').forEach(line => {
if (line.includes('::error file=')) {
const match = line.match(/::error file=(.+?)::(.+)/);
if (match) {
const [_, file, message] = match;
commentBody += `- ${file}: ${message}\n`;
}
}
});
commentBody += '\nPlease remove these debugging statements before merging.';
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: owner,
repo: repo,
body: commentBody
});
- name: Show Results
if: failure() && github.event_name == 'workflow_dispatch'
run: |
echo "::group::Debug Check Results"
echo "$DEBUG_OUTPUT"
echo "::endgroup::"
echo "❌ Debug statements were found. Please remove them before proceeding."
exit 1
# 3. Testing jobs (grouped together)
build:
needs: [check-if-merged, debug-check]
if: needs.check-if-merged.outputs.should_run == 'true'
runs-on: blacksmith-2vcpu-ubuntu-2404 # trunk-ignore(actionlint/runner-label)
environment: ci
strategy:
matrix:
node-version: [22, 24]
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v7 # zizmor: ignore[unpinned-uses]
with:
persist-credentials: false
token: ${{ secrets.ACTIONS_KEY }}
- uses: pnpm/action-setup@v6 # zizmor: ignore[unpinned-uses]
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v7 # zizmor: ignore[unpinned-uses]
with:
node-version: ${{ matrix.node-version }}
- name: Install
run: |
pnpm install --filter . --prefer-offline --frozen-lockfile --ignore-scripts --config.engine-strict=false
- name: Test
run: |
pnpm build
pnpm test
- name: Upload coverage to Coveralls
uses: coverallsapp/github-action@v2 # zizmor: ignore[unpinned-uses]
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
flag-name: node-${{ matrix.node-version }}
parallel: true
fail-on-error: false
if: matrix.node-version == '22'
# Removed dependency caching to reduce cache-poisoning risk
playwright-tests:
needs: [check-if-merged, debug-check]
if: needs.check-if-merged.outputs.should_run == 'true' && needs.check-if-merged.outputs.has_skip_label != 'true'
timeout-minutes: 60
runs-on: blacksmith-4vcpu-ubuntu-2404 # trunk-ignore(actionlint/runner-label)
environment: ci
steps:
- uses: actions/checkout@v7 # zizmor: ignore[unpinned-uses]
with:
persist-credentials: false
token: ${{ secrets.ACTIONS_KEY }}
- uses: pnpm/action-setup@v6 # zizmor: ignore[unpinned-uses]
- uses: actions/setup-node@v7 # zizmor: ignore[unpinned-uses]
with:
node-version: 24
- name: Install dependencies
run: pnpm install --filter . --prefer-offline --frozen-lockfile --ignore-scripts --config.engine-strict=false
- name: Install Playwright Browsers
run: pnpm exec playwright install --with-deps
- name: Run Playwright tests
run: pnpm run test:e2e
- uses: actions/upload-artifact@v7 # zizmor: ignore[unpinned-uses]
if: always()
with:
name: playwright-results
path: test-results/
retention-days: 10
# 4. Coverage reporting (depends on tests)
coverage-report:
needs: [check-if-merged, build, playwright-tests]
if: |
always() &&
needs.check-if-merged.outputs.should_run == 'true' &&
needs.build.result == 'success' &&
(needs.playwright-tests.result == 'success' || needs.playwright-tests.result == 'skipped')
runs-on: blacksmith-2vcpu-ubuntu-2404 # trunk-ignore(actionlint/runner-label)
steps:
- name: Coveralls Finished
uses: coverallsapp/github-action@v2 # zizmor: ignore[unpinned-uses]
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
parallel-finished: true
fail-on-error: false
# 5. Publishing job (main deployment logic)
publish-github-packages:
needs: [check-if-merged, build, playwright-tests, coverage-report]
if: |
always() &&
needs.check-if-merged.outputs.should_run == 'true' &&
needs.build.result == 'success' &&
(needs.playwright-tests.result == 'success' || needs.playwright-tests.result == 'skipped') &&
(needs.coverage-report.result == 'success' || needs.coverage-report.result == 'skipped')
runs-on: ubuntu-latest
environment: production
permissions:
contents: write
packages: write
issues: write
pull-requests: write
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v7 # zizmor: ignore[unpinned-uses]
with:
persist-credentials: false
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- uses: pnpm/action-setup@v6 # zizmor: ignore[unpinned-uses]
- name: Use Node.js - 24
uses: actions/setup-node@v7 # zizmor: ignore[unpinned-uses,cache-poisoning]
with:
node-version: 24
- name: Install
run: pnpm install --filter . --prefer-offline --frozen-lockfile --ignore-scripts --config.engine-strict=false
- name: Check Publishing Status
id: publish-check
env:
EVENT_NAME: ${{ github.event_name }}
IS_MERGED: ${{ github.event.pull_request.merged }}
HAS_SKIP_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'skip-publish') }}
INPUT_VERSION: ${{ github.event.inputs.version_bump }}
run: |
# Validate event name first
if [[ "$EVENT_NAME" != "pull_request" && "$EVENT_NAME" != "workflow_dispatch" ]]; then
echo "Invalid event type"
exit 1
fi
# Check publishing conditions
if [[ "$EVENT_NAME" == "pull_request" ]]; then
if [[ "$IS_MERGED" != "true" ]]; then
echo "PR not merged, skipping publish"
echo "should_publish=false" >> $GITHUB_OUTPUT
elif [[ "$HAS_SKIP_LABEL" == "true" ]]; then
echo "Publishing skipped due to skip-publish label"
echo "should_publish=false" >> $GITHUB_OUTPUT
else
echo "should_publish=true" >> $GITHUB_OUTPUT
fi
elif [[ "$EVENT_NAME" == "workflow_dispatch" && "$INPUT_VERSION" == "skip" ]]; then
echo "Publishing skipped via manual selection"
echo "should_publish=false" >> $GITHUB_OUTPUT
else
echo "should_publish=true" >> $GITHUB_OUTPUT
fi
- name: Create Comment on Skip
if: github.event_name == 'pull_request' && steps.publish-check.outputs.should_publish == 'false'
uses: actions/github-script@v9 # zizmor: ignore[unpinned-uses]
with:
github-token: ${{ secrets.ACTIONS_KEY }}
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '⏭️ NPM publishing was skipped due to the `skip-publish` label.'
})
- name: Import GPG key
if: steps.publish-check.outputs.should_publish == 'true'
id: import_gpg
uses: crazy-max/ghaction-import-gpg@v7 # zizmor: ignore[unpinned-uses]
with:
gpg_private_key: ${{ secrets.ACTIONS_GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.ACTIONS_GPG_PASSPHRASE }}
git_user_signingkey: true
git_commit_gpgsign: true
git_tag_gpgsign: true
git_config_global: true
- name: Determine version bump type
if: steps.publish-check.outputs.should_publish == 'true'
id: version-type
env:
HAS_MAJOR: ${{ contains(github.event.pull_request.labels.*.name, 'major') }}
HAS_MINOR: ${{ contains(github.event.pull_request.labels.*.name, 'minor') }}
INPUT_VERSION: ${{ github.event.inputs.version_bump }}
run: |
# Function to validate version bump type
validate_bump_type() {
local bump="$1"
case "$bump" in
"major"|"minor"|"patch"|"skip") echo "$bump" ;;
*) echo "patch" ;;
esac
}
# Determine and validate bump type
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
BUMP_TYPE=$(validate_bump_type "$INPUT_VERSION")
if [ "$BUMP_TYPE" = "skip" ]; then
echo "Publishing skipped via manual selection"
echo "should_publish=false" >> $GITHUB_OUTPUT
exit 0
fi
elif [ "$HAS_MAJOR" = "true" ]; then
BUMP_TYPE="major"
elif [ "$HAS_MINOR" = "true" ]; then
BUMP_TYPE="minor"
else
BUMP_TYPE="patch"
fi
echo "bump=$BUMP_TYPE" >> $GITHUB_OUTPUT
- name: Bump version
if: steps.publish-check.outputs.should_publish == 'true'
id: version
env:
PR_TITLE: ${{ github.event.pull_request.title }}
PR_URL: ${{ github.event.pull_request.html_url }}
BUMP_TYPE: ${{ steps.version-type.outputs.bump }}
GITHUB_TOKEN: ${{ secrets.ACTIONS_KEY }}
GPG_KEY_ID: ${{ steps.import_gpg.outputs.keyid }}
run: |
# Validate GPG key ID format first
if [[ ! "$GPG_KEY_ID" =~ ^[A-F0-9]{16}$ ]]; then
echo "Invalid GPG key ID format"
exit 1
fi
# Configure git with validated credentials
git config --global user.name "GitHub Actions Bot"
git config --global user.email "jason@humanspeak.com"
git config --global commit.gpgsign true
git config --global user.signingkey "$GPG_KEY_ID"
# Set up authentication for push
git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git"
# Validate bump type for security
case "$BUMP_TYPE" in
"major"|"minor"|"patch") ;;
*)
echo "Invalid version bump type"
exit 1
;;
esac
# Mutate package.json only — do NOT parse pnpm stdout (pnpm 11 prints multi-line text)
pnpm version "$BUMP_TYPE" --no-git-tag-version
PACKAGE_VERSION=$(node -p "require('./package.json').version")
NEW_VERSION="v${PACKAGE_VERSION}"
echo "new_version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
# Keep tombstone shims in lockstep with the canonical version
for SHIM in tombstones/svelte-diff tombstones/svelte-diff-match-patch; do
node -e "
const fs = require('fs')
const path = process.argv[1] + '/package.json'
const pkg = JSON.parse(fs.readFileSync(path, 'utf8'))
pkg.version = process.argv[2]
pkg.dependencies['@humanspeak/svelte-diff'] = '^' + process.argv[2]
fs.writeFileSync(path, JSON.stringify(pkg, null, 4) + '\n')
" "$SHIM" "$PACKAGE_VERSION"
done
# Escape special characters in PR title and URL
ESCAPED_TITLE=$(echo "$PR_TITLE" | sed 's/[`$"\]/\\&/g')
ESCAPED_URL=$(echo "$PR_URL" | sed 's/[`$"\]/\\&/g')
# Refresh the managed README footer (the docs-kit ecosystem block +
# standardized License/Credits). Runs AFTER `pnpm version` so the
# working tree is clean when pnpm runs — pnpm version aborts on a
# dirty tree. Fetched raw from the private docs-kit repo (no install,
# no dependency here); reuses $GITHUB_TOKEN (ACTIONS_KEY) for auth.
# Best-effort: a failed fetch or roster blip only warns.
UPDATER="$RUNNER_TEMP/update-ecosystem-readme.mjs"
if curl -fsSL -H "Authorization: token $GITHUB_TOKEN" \
https://raw.githubusercontent.com/humanspeak/docs-kit/main/scripts/update-ecosystem-readme.mjs \
-o "$UPDATER"; then
node "$UPDATER" || echo "::warning::ecosystem updater errored; README left unchanged"
else
echo "::warning::could not fetch ecosystem updater; skipping README refresh"
fi
# Commit the version changes (and any refreshed README ecosystem block)
git add package.json README.md tombstones/svelte-diff/package.json tombstones/svelte-diff-match-patch/package.json
git commit -m "Bump version to ${NEW_VERSION} [skip ci]"
# Create an annotated tag with release notes
git tag -a "${NEW_VERSION}" -m "Release ${NEW_VERSION}
Changes in this Release:
- ${ESCAPED_TITLE}
PR: ${ESCAPED_URL}"
# Push changes
git push
git push --tags
- name: Create Release
if: steps.publish-check.outputs.should_publish == 'true'
id: create_release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NEW_VERSION: ${{ steps.version.outputs.new_version }}
EVENT_NAME: ${{ github.event_name }}
CUSTOM_MESSAGE: ${{ github.event.inputs.custom_message }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_URL: ${{ github.event.pull_request.html_url }}
run: |
BODY="Changes in this Release
${CUSTOM_MESSAGE:-$PR_TITLE}"
if [[ "$EVENT_NAME" == "pull_request" ]]; then
BODY="$BODY
For more details, see the [Pull Request]($PR_URL)"
fi
gh release create "$NEW_VERSION" \
--title "Release $NEW_VERSION" \
--notes "$BODY" \
--latest
- name: Publish
if: steps.publish-check.outputs.should_publish == 'true'
run: |
# Clean up any existing npmrc files that might interfere with OIDC
rm -f ./.npmrc ~/.npmrc
# Configure registry for scoped package
pnpm config set @humanspeak:registry https://registry.npmjs.org/
# Publish using pnpm with OIDC (trusted publishing)
pnpm publish --provenance --access public --no-git-checks
- name: Publish tombstone shims
if: steps.publish-check.outputs.should_publish == 'true'
run: |
# Shims re-export the canonical package under the old/alias names.
# Best-effort: a shim failure must not trigger "Cleanup on failure"
# and roll back the main release that already shipped.
# The old-name shim carries a "deprecated" field in its manifest, so
# each published version arrives pre-deprecated — no npm deprecate
# call (or extra token) needed here.
pnpm --dir tombstones/svelte-diff-match-patch publish --provenance --access public --no-git-checks ||
echo "::warning::@humanspeak/svelte-diff-match-patch shim publish failed — publish manually from tombstones/"
pnpm --dir tombstones/svelte-diff publish --provenance --access public --no-git-checks ||
echo "::warning::svelte-diff alias publish failed — publish manually from tombstones/"
- name: Cleanup on failure
if: failure()
env:
RELEASE_VERSION: ${{ steps.version.outputs.new_version }}
GITHUB_TOKEN: ${{ secrets.ACTIONS_KEY }}
run: |
# Validate version format first
if [[ ! "$RELEASE_VERSION" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Invalid version format: $RELEASE_VERSION"
exit 1
fi
# Proceed with cleanup only if validation passes
gh release delete "$RELEASE_VERSION" --yes || true
git tag -d "$RELEASE_VERSION" || true
git push --delete origin "$RELEASE_VERSION" || true
- name: Notify on failure
if: failure()
uses: actions/github-script@v9 # zizmor: ignore[unpinned-uses]
with:
github-token: ${{ secrets.ACTIONS_KEY }}
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '❌ Release workflow failed. Please check the [workflow logs](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})'
})