Skip to content

chore(cicd): setup github actions and release workflow #31

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 11, 2025
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
60 changes: 60 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: CI

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [18.x, 20.x]

steps:
- uses: actions/checkout@v4

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}

- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 10.8.0

- name: Get pnpm store directory
id: pnpm-cache
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT

- name: Setup pnpm cache
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-

- name: Install dependencies
run: pnpm install

- name: Lint
run: pnpm lint

- name: Build
run: pnpm build

- name: Test
run: pnpm test:ci

- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
73 changes: 73 additions & 0 deletions .github/workflows/pr-changelog.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: PR Changelog

on:
pull_request:
types: [opened, synchronize, reopened, edited]
branches: [ main ]

jobs:
validate-pr:
name: Validate PR Description
runs-on: ubuntu-latest

steps:
- name: Check PR Description
id: check-pr
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const body = pr.body || '';

// Check if PR has a changelog section
const hasChangelog = body.includes('## Changelog') ||
body.includes('## Changes') ||
body.includes('## What Changed');

if (!hasChangelog) {
core.setFailed('PR description should include a changelog section (## Changelog, ## Changes, or ## What Changed)');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: '⚠️ Please add a changelog section to your PR description. This will be used in release notes.\n\nAdd one of these sections:\n- `## Changelog`\n- `## Changes`\n- `## What Changed`\n\nAnd describe the changes in a user-friendly way.'
});
return;
}

core.info('PR has a valid changelog section');

- name: Add Label
if: success()
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;

// Determine PR type based on title or labels
let prType = 'other';
const title = pr.title.toLowerCase();

if (title.startsWith('fix:') || title.includes('bug') || title.includes('fix')) {
prType = 'fix';
} else if (title.startsWith('feat:') || title.includes('feature')) {
prType = 'feature';
} else if (title.includes('breaking') || title.includes('!:')) {
prType = 'breaking';
} else if (title.startsWith('docs:') || title.includes('documentation')) {
prType = 'docs';
} else if (title.startsWith('chore:') || title.includes('chore')) {
prType = 'chore';
}

// Add appropriate label
try {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: [`type: ${prType}`]
});
} catch (error) {
core.warning(`Failed to add label: ${error.message}`);
}
58 changes: 58 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Release

on:
release:
types: [published]

jobs:
release:
name: Release
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
registry-url: 'https://registry.npmjs.org'

- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 10.8.0

- name: Get pnpm store directory
id: pnpm-cache
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT

- name: Setup pnpm cache
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-

- name: Install dependencies
run: pnpm install

- name: Lint
run: pnpm lint

- name: Build
run: pnpm build

- name: Test
run: pnpm test:ci

- name: Publish to NPM
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: pnpm publish --no-git-checks
143 changes: 143 additions & 0 deletions .github/workflows/version-bump.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
name: Version Bump

on:
push:
branches: [ main ]

jobs:
version-bump:
name: Version Bump
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, 'skip ci') && !contains(github.event.head_commit.message, 'chore(release)')"

steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN }}

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20

- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 10.8.0

- name: Get pnpm store directory
id: pnpm-cache
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT

- name: Setup pnpm cache
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-

- name: Install dependencies
run: pnpm install

- name: Lint
run: pnpm lint

- name: Build
run: pnpm build

- name: Test
run: pnpm test:ci

- name: Test Release (Dry Run)
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN }}
run: pnpm release:dry-run

- name: Update or Create GitHub Release Draft
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN }}
run: |
# Get the next version from semantic-release
VERSION=$(npx semantic-release --dry-run | grep -oP 'The next release version is \K[0-9]+\.[0-9]+\.[0-9]+' || echo "")

if [ -z "$VERSION" ]; then
echo "No version change detected, skipping release creation"
exit 0
fi

echo "Next version will be: $VERSION"

# Update package.json version
npm version $VERSION --no-git-tag-version

# Generate changelog for this version
npx semantic-release --dry-run --no-ci > release-notes.md

# Extract just the release notes section
sed -n '/# \[/,/^$/p' release-notes.md > changelog-extract.md

# Get PR information
PR_NUMBER=$(echo "${{ github.event.head_commit.message }}" | grep -oP '#\K[0-9]+' || echo "")
PR_TITLE=""
PR_BODY=""

if [ ! -z "$PR_NUMBER" ]; then
PR_INFO=$(gh pr view $PR_NUMBER --json title,body || echo "{}")
PR_TITLE=$(echo "$PR_INFO" | jq -r '.title // ""')
PR_BODY=$(echo "$PR_INFO" | jq -r '.body // ""')
fi

# Check if draft release already exists
RELEASE_EXISTS=$(gh release view v$VERSION --json isDraft 2>/dev/null || echo "{}")
IS_DRAFT=$(echo "$RELEASE_EXISTS" | jq -r '.isDraft // false')

if [ "$IS_DRAFT" = "true" ]; then
echo "Updating existing draft release v$VERSION"

# Get existing release notes
gh release view v$VERSION --json body | jq -r '.body' > existing-notes.md

# Add new PR information if available
if [ ! -z "$PR_NUMBER" ] && [ ! -z "$PR_TITLE" ]; then
echo -e "\n### PR #$PR_NUMBER: $PR_TITLE\n" >> existing-notes.md
if [ ! -z "$PR_BODY" ]; then
echo -e "$PR_BODY\n" >> existing-notes.md
fi
fi

# Update the release
gh release edit v$VERSION --notes-file existing-notes.md
else
echo "Creating new draft release v$VERSION"

# Create initial release notes
echo -e "# Release v$VERSION\n" > release-notes.md
cat changelog-extract.md >> release-notes.md

# Add PR information if available
if [ ! -z "$PR_NUMBER" ] && [ ! -z "$PR_TITLE" ]; then
echo -e "\n## Pull Requests\n" >> release-notes.md
echo -e "### PR #$PR_NUMBER: $PR_TITLE\n" >> release-notes.md
if [ ! -z "$PR_BODY" ]; then
echo -e "$PR_BODY\n" >> release-notes.md
fi
fi

# Create a draft release
gh release create v$VERSION \
--draft \
--title "v$VERSION" \
--notes-file release-notes.md
fi

# Commit the version change
git config --global user.name "GitHub Actions"
git config --global user.email "[email protected]"
git add package.json
git commit -m "chore(release): bump version to $VERSION [skip ci]"
git push
18 changes: 18 additions & 0 deletions .releaserc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"branches": ["main"],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
"@semantic-release/npm",
["@semantic-release/github", {
"assets": [
{"path": "dist/index.js", "label": "MCP Server Bundle"}
]
}],
["@semantic-release/git", {
"assets": ["package.json", "CHANGELOG.md"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}]
]
}