Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 159 additions & 3 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,56 @@ Changelogs are generated using git-cliff with the following approach:
3. **Format:** Follows [Keep a Changelog](https://keepachangelog.com/) format
4. **Grouping:** Commits are grouped by type (Added, Fixed, Changed, etc.)

### GitHub Integration Features

When `GITHUB_TOKEN` is provided, git-cliff enhances changelogs with:

- **PR Links**: Automatic links to pull requests in commit messages
- Format: `- commit message by @username in [#123](url)`
- Works with squash merges and regular merges

- **Contributor Attribution**: Shows who made each change
- Format: `by @username` after each commit message
- Retrieved from GitHub API

- **First-Time Contributors**: Special recognition section
- Shows new contributors separately: `### New Contributors`
- Format: `- @username made their first contribution in [#123](url)`

- **Statistics**: Release metrics automatically calculated
- Commit count (total and conventional)
- Linked issues/PRs count
- Days since last release

- **PR Labels**: Can be used for filtering
- Add `skip-release-notes` label to exclude commits from changelog
- Supports label-based grouping (advanced feature)

**Example changelog entry with GitHub integration:**

```markdown
## [0.2.0] - 2025-12-27

### Added
- feat: add search functionality by @username in [#42](https://github.com/JSONbored/safemocker/pull/42)
- feat: improve performance by @contributor in [#43](https://github.com/JSONbored/safemocker/pull/43)

### New Contributors
- @newuser made their first contribution in [#42](https://github.com/JSONbored/safemocker/pull/42)

### Statistics
- 5 commits in this release
- 4 conventional commits
- 2 linked issues/PRs
- 3 days since last release
```

**Configuration:**

- `GITHUB_TOKEN` is automatically provided in workflows via `secrets.GITHUB_TOKEN`
- `[remote.github]` section in `cliff.toml` configures repository details
- GitHub API is used to fetch PR metadata, contributor info, and labels

## Manual Operations

### Trigger Version Bump Manually
Expand Down Expand Up @@ -165,18 +215,34 @@ Changelogs are generated using git-cliff with the following approach:
**Solutions:**
- Verify commits since last tag exist
- Check git-cliff output in workflow logs
- Ensure `GITHUB_TOKEN` is set (for PR links)
- Ensure `GITHUB_TOKEN` is set (for PR links and GitHub integration)
- Verify `cliff.toml` configuration is correct
- Check that commits follow conventional commit format

### GitHub Integration Not Working

**Issue:** PR links, usernames, or contributor info not appearing in changelog.

**Solutions:**
- Verify `GITHUB_TOKEN` is set in workflow (automatically provided via `secrets.GITHUB_TOKEN`)
- Check that commits are associated with PRs (squash merges work fine)
- Ensure `[remote.github]` is configured in `cliff.toml` with correct owner/repo
- Verify GitHub API rate limits haven't been exceeded
- Check workflow logs for GitHub API errors
- Note: GitHub integration requires commits to be associated with PRs - direct commits to main won't have PR links

### npm Publish Fails

**Issue:** npm publish fails with authentication error.

**Solutions:**
- Verify trusted publishing is configured in npm
- Verify trusted publishing is configured in npm for repository and environment
- Check `production` environment is set in workflow
- Ensure `id-token: write` permission is set
- Verify `setup-node` is configured **before** `pnpm/action-setup`
- Check `registry-url: 'https://registry.npmjs.org'` is set
- Verify package name and version in `package.json`
- See "npm Trusted Publishing" section above for detailed troubleshooting

### Tag Already Exists

Expand Down Expand Up @@ -204,13 +270,103 @@ Contains:
- npm scripts (optional, for local use)
- Package metadata

## Squash Merge Support

### How It Works

When you **squash merge** a PR, GitHub creates a **single commit** on `main` with:
- **Commit message**: The PR title (should follow conventional commits: `feat:`, `fix:`, etc.)
- **Commit body**: The PR description (optional)

**git-cliff automatically processes all commits**, including squash merge commits. The workflow:

1. ✅ Squash merge creates a single commit on `main`
2. ✅ git-cliff processes this commit just like any other commit
3. ✅ Commit message is parsed using conventional commit rules
4. ✅ Changelog entry is generated based on commit type (feat → Added, fix → Fixed, etc.)

### Best Practices for Squash Merges

- **Use conventional commits in PR titles**: `feat: add feature`, `fix: resolve bug`, etc.
- **Include PR references**: The commit message can include `(#123)` which will be converted to a PR link
- **PR descriptions are optional**: git-cliff primarily uses the commit message (PR title)

### Example

**PR Title**: `feat: add search functionality (#42)`

**After squash merge**:
- Creates commit: `feat: add search functionality (#42)`
- git-cliff processes it as a `feat:` commit
- Appears in changelog under "### Added"
- PR link is automatically added: `([#42](https://github.com/JSONbored/safemocker/pull/42))`

### Configuration

The `commit_preprocessors` in `cliff.toml` handle:
- Standard GitHub PR merge messages: `Merge pull request #123`
- PR references in commit messages: `(#123)`
- Squash merge commits (processed automatically)

## npm Trusted Publishing

### Overview

The release workflow uses **npm Trusted Publishing** with OIDC (OpenID Connect) for secure, tokenless authentication. This eliminates the need for `NODE_AUTH_TOKEN` secrets.

### Requirements

1. **Environment name**: Must match npm trusted publisher configuration (currently `production`)
2. **Permissions**: `id-token: write` is required (already set in workflow)
3. **setup-node order**: Must be configured **before** pnpm setup for OIDC to work
4. **registry-url**: Must be set to `https://registry.npmjs.org`
5. **--provenance flag**: Creates signed provenance statements

### How It Works

1. GitHub Actions generates an OIDC token automatically
2. `setup-node@v4` with `registry-url` configures npm to use OIDC
3. `npm publish --provenance` uses the OIDC token for authentication
4. npm verifies the token against your trusted publisher configuration
5. Package is published with signed provenance

### Verification

After publishing, the workflow verifies the package is available on npm:

```bash
npm view "@jsonbored/safemocker@$VERSION" version
```

If this fails, the workflow will report an error.

### Troubleshooting npm Trusted Publishing

**Issue**: npm publish fails with authentication error

**Solutions**:
1. Verify environment name matches npm trusted publisher config (`production`)
2. Check that `id-token: write` permission is set in workflow
3. Ensure `setup-node` is configured before `pnpm/action-setup`
4. Verify `registry-url: 'https://registry.npmjs.org'` is set
5. Check npm trusted publisher configuration: https://docs.npmjs.com/trusted-publishers/

**Issue**: Package not found after publish

**Solutions**:
1. Wait a few seconds - npm registry may need time to update
2. Check npm registry directly: https://www.npmjs.com/package/@jsonbored/safemocker
3. Verify the version number matches exactly
4. Check workflow logs for publish errors

## Best Practices

1. **Use Conventional Commits:** Always use conventional commit format (`feat:`, `fix:`, etc.)
2. **Squash Merges:** Use squash merges for PRs to maintain clean history
2. **Squash Merges:** Use squash merges for PRs to maintain clean history (fully supported)
3. **Don't Commit Changelog Manually:** Let workflows handle changelog updates
4. **Test Locally:** Use `pnpm exec git-cliff --bumped-version` to preview version
5. **Review Before Merging:** Check PR commits to ensure correct version bump
6. **Verify npm Publishing:** Check workflow logs to ensure package was published successfully

## Local Development

Expand Down
30 changes: 27 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- name: Setup pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
with:
version: 10

- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '20'
cache: 'pnpm'
Expand All @@ -47,3 +47,27 @@ jobs:
run: pnpm test:coverage
continue-on-error: true

- name: Validate changelog format
run: |
if [ -f CHANGELOG.md ]; then
# Verify changelog has proper structure
if ! grep -q "^# Changelog" CHANGELOG.md; then
echo "❌ Error: Invalid changelog header - expected '# Changelog'"
exit 1
fi

# Verify changelog follows Keep a Changelog format
if ! grep -q "Keep a Changelog" CHANGELOG.md; then
echo "⚠️ Warning: Changelog may not follow Keep a Changelog format"
fi

# Verify changelog has at least one version entry or [Unreleased] section
if ! grep -qE "^## \[" CHANGELOG.md; then
echo "⚠️ Warning: Changelog has no version entries"
fi

echo "✅ Changelog format validation passed"
else
echo "ℹ️ CHANGELOG.md not found (this is okay for new repositories)"
fi

84 changes: 75 additions & 9 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@
# **Triggers:**
# - Tag push matching v*.*.* pattern (automatic, triggered by version-bump.yml)
# - Manual workflow_dispatch (with tag input for re-publishing)
#
# **npm Trusted Publishing Requirements:**
# CRITICAL: This workflow filename (publish-release.yml) MUST exactly match the
# workflow filename configured in npm trusted publisher settings.
#
# Required npm trusted publisher configuration:
# - Organization/User: JSONbored
# - Repository: safemocker (or JSONbored/safemocker)
# - Workflow filename: publish-release.yml (must match this file name exactly)
# - Environment: production (must match the environment name below)
#
# See: https://docs.npmjs.com/trusted-publishers/
# Documentation: .cursor/npm-trusted-publishing-setup.md
name: Publish Release

on:
Expand All @@ -32,7 +45,13 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10

# CRITICAL: Must match npm trusted publisher environment configuration
# CRITICAL: Environment name must exactly match npm trusted publisher configuration
# This workflow filename (publish-release.yml) must also match npm config
# Required npm settings:
# - Workflow filename: publish-release.yml
# - Environment: production
# - Repository: JSONbored/safemocker
# - Organization: JSONbored
environment: production

permissions:
Expand All @@ -41,7 +60,7 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
# Full history needed for changelog extraction
fetch-depth: 0
Expand Down Expand Up @@ -79,20 +98,28 @@ jobs:
echo "TAG=$TAG" >> $GITHUB_OUTPUT
echo "✅ Extracted version: $VERSION from tag: $TAG"

- name: Setup Node.js
uses: actions/setup-node@v4
- name: Setup Node.js (with OIDC for trusted publishing)
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
cache: 'pnpm'
# OIDC is automatically used when id-token: write permission is set
# CRITICAL: setup-node must be configured BEFORE pnpm setup for OIDC to work correctly
# OIDC token is automatically used when id-token: write permission is set
# No NODE_AUTH_TOKEN needed for trusted publishing
# Note: cache is configured after pnpm is installed

- name: Setup pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
with:
version: 10

- name: Configure Node.js cache
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '20'
cache: 'pnpm'
# Configure caching after pnpm is available

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

Expand Down Expand Up @@ -128,14 +155,53 @@ jobs:

echo "🔍 DEBUG: Publishing @jsonbored/safemocker@$VERSION to npm..."
echo "🔍 DEBUG: Using trusted publishing (OIDC) - no NODE_AUTH_TOKEN needed"
echo "🔍 DEBUG: Environment: ${{ github.environment }}"
echo "🔍 DEBUG: Repository: ${{ github.repository }}"

# For trusted publishing, npm automatically uses OIDC token from GitHub Actions
# The --provenance flag creates a signed provenance statement
# Ensure trusted publishing is configured in npm: https://docs.npmjs.com/trusted-publishers/
npm publish --access public --provenance
# Trusted publishing requires:
# 1. Environment name matches npm trusted publisher config (currently: production)
# 2. id-token: write permission (already set in permissions)
# 3. registry-url set in setup-node (already configured)

if ! npm publish --access public --provenance; then
echo "❌ Error: npm publish failed" >&2
echo "🔍 DEBUG: Check that trusted publishing is configured in npm for:" >&2
echo " - Organization/User: JSONbored" >&2
echo " - Repository: ${{ github.repository }}" >&2
echo " - Workflow filename: publish-release.yml (must match exactly)" >&2
echo " - Environment: production (must match exactly)" >&2
echo " - See: https://docs.npmjs.com/trusted-publishers/" >&2
echo " - Documentation: .cursor/npm-trusted-publishing-setup.md" >&2
exit 1
fi

echo "✅ Published @jsonbored/safemocker@$VERSION to npm"

- name: Verify npm publish
run: |
set -e # Exit on error

VERSION="${{ steps.version.outputs.VERSION }}"

echo "🔍 DEBUG: Verifying package is published on npm..."

# Wait a moment for npm registry to update
sleep 2

# Verify package exists on npm
if ! npm view "@jsonbored/safemocker@$VERSION" version > /dev/null 2>&1; then
echo "❌ Error: Package not found on npm after publish" >&2
echo "🔍 DEBUG: Attempted to verify: @jsonbored/safemocker@$VERSION" >&2
echo "🔍 DEBUG: This may indicate the publish failed or npm registry hasn't updated yet" >&2
exit 1
fi

PUBLISHED_VERSION=$(npm view "@jsonbored/safemocker@$VERSION" version)
echo "✅ Verified: @jsonbored/safemocker@$PUBLISHED_VERSION is published on npm"

- name: Extract changelog for version
id: changelog
run: |
Expand Down Expand Up @@ -196,7 +262,7 @@ jobs:
head -10 /tmp/release-notes.md || echo "(empty)"

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2
with:
tag_name: ${{ steps.version.outputs.TAG }}
name: ${{ steps.version.outputs.TAG }}
Expand Down
Loading
Loading