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
50 changes: 50 additions & 0 deletions .claude/commands/prepare-release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Prepare Release

Run all pre-release checks and prepare a version bump for the automated release pipeline.

## Step 1: Determine Version

Ask the user what version to release. Show the current version from `package.json` and ask:

- **What type of version bump?** Options: patch, minor, major, prerelease (e.g. `1.0.0-rc.1`), or custom version string.
- If prerelease, ask for the prerelease identifier (e.g. `rc.1`, `beta.1`).
- Show what the new version will be before proceeding. Confirm with the user.

## Step 2: Check Working Tree

Run `git status` to verify the working tree is clean (no uncommitted changes). If there are uncommitted changes, warn the user and ask whether to proceed or abort.

## Step 3: Bump Version

Update the version in `package.json` using the appropriate method:
- For patch/minor/major: run `npm version <type> --no-git-tag-version`
- For prerelease or custom: edit `package.json` directly with the exact version string

## Step 4: Run Quality Checks

Run these checks sequentially. Stop and report on the first failure:

1. **Lint**: `npm run lint`
2. **Format**: `npm run format:check` — if this fails, run `npm run format` to auto-fix, then re-check
3. **Type Check**: `npm run typecheck`
4. **Tests**: `npx jest --passWithNoTests`
5. **Build**: `npm run build`
6. **Security Audit**: `npm audit --audit-level moderate --omit=dev`

If any check fails (except format, which auto-fixes), stop and help the user fix the issue before continuing.

## Step 5: Commit and Push

After all checks pass:

1. Stage `package.json` (and any files changed by formatting)
2. Create a commit with message: `chore: bump version to <new-version>`
3. Push to the current branch

## Step 6: Summary

Print a summary:
- Previous version → New version
- All checks passed
- Branch and commit hash
- Remind the user: "Merge this branch to `main` to trigger the automated release pipeline."
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
# Your SVGMaker API Key (required)
SVGMAKER_API_KEY=your-api-key-here

# Optional: Custom API base URL (default: https://svgmaker.io/api)
# SVGMAKER_BASE_URL=https://svgmaker.io/api
# Optional: Custom API base URL (default: https://api.svgmaker.io)
# SVGMAKER_BASE_URL=https://api.svgmaker.io

# Optional: Request timeout in milliseconds (default: 30000)
# SVGMAKER_TIMEOUT=60000
Expand Down
32 changes: 32 additions & 0 deletions .github/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Configuration for GitHub's automatically generated release notes
# https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes

changelog:
exclude:
labels:
- skip-changelog
authors:
- dependabot
- github-actions
categories:
- title: Breaking Changes
labels:
- breaking
- semver-major
- title: New Features
labels:
- feature
- enhancement
- semver-minor
- title: Bug Fixes
labels:
- bug
- fix
- semver-patch
- title: Documentation
labels:
- documentation
- docs
- title: Other Changes
labels:
- "*"
223 changes: 145 additions & 78 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,133 +2,200 @@ name: Release

on:
push:
tags:
- 'v*.*.*'
branches:
- main

permissions:
id-token: write # Required for OIDC
contents: read
contents: write
id-token: write
models: read

jobs:
validate-release:
detect-release:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.validate.outputs.version }}
should-release: ${{ steps.validate.outputs.should-release }}
version: ${{ steps.detect.outputs.version }}
should-release: ${{ steps.detect.outputs.should-release }}

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Validate version and tag match
id: validate
with:
fetch-depth: 2

- name: Detect version change
id: detect
run: |
# Extract version from git tag
TAG_VERSION=${GITHUB_REF#refs/tags/v}
echo "Tag version: $TAG_VERSION"

# Get version from package.json
PACKAGE_VERSION=$(node -p "require('./package.json').version")
echo "Package version: $PACKAGE_VERSION"

# Check if versions match
if [ "$TAG_VERSION" != "$PACKAGE_VERSION" ]; then
echo "❌ Version mismatch: tag ($TAG_VERSION) != package.json ($PACKAGE_VERSION)"
echo "should-release=false" >> $GITHUB_OUTPUT
exit 1
fi

echo "version=$PACKAGE_VERSION" >> $GITHUB_OUTPUT

# Check if version already exists on npm
if npm view "@genwave/svgmaker-sdk@$PACKAGE_VERSION" version >/dev/null 2>&1; then
echo "should-release=false" >> $GITHUB_OUTPUT
echo "❌ Version $PACKAGE_VERSION already exists on npm - skipping release"
NEW_VERSION=$(node -p "require('./package.json').version")
# Get previous commit's package.json version
OLD_VERSION=$(git show HEAD~1:package.json | node -p "JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')).version")
if [ "$NEW_VERSION" != "$OLD_VERSION" ]; then
# Check npm
if npm view "@genwave/svgmaker-sdk@$NEW_VERSION" version >/dev/null 2>&1; then
echo "Version $NEW_VERSION already on npm, skipping"
echo "should-release=false" >> $GITHUB_OUTPUT
else
echo "Version changed: $OLD_VERSION -> $NEW_VERSION"
echo "should-release=true" >> $GITHUB_OUTPUT
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
fi
else
echo "should-release=true" >> $GITHUB_OUTPUT
echo "✅ Version $PACKAGE_VERSION is valid and ready for release"
echo "No version change, skipping"
echo "should-release=false" >> $GITHUB_OUTPUT
fi

release:
runs-on: ubuntu-latest
needs: validate-release
if: needs.validate-release.outputs.should-release == 'true'
needs: detect-release
if: needs.detect-release.outputs.should-release == 'true'
permissions:
contents: write
id-token: write

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

- name: Use Node.js 20.x
uses: actions/setup-node@v6
with:
node-version: 20.x
cache: 'npm'
registry-url: 'https://registry.npmjs.org'

- name: Update npm for OIDC support
- name: Update npm
run: npm install -g npm@latest

- name: Install dependencies
run: npm ci

- name: Run tests
run: npx jest --passWithNoTests --no-coverage

- name: Run linting
run: npm run lint

- name: Build package
run: npm run build

- name: Generate changelog

- name: Generate release notes from GitHub
id: github_notes
uses: actions/github-script@v7
with:
script: |
const version = '${{ needs.detect-release.outputs.version }}';
const tagName = `v${version}`;

// Find previous tag
let previousTag;
try {
const tags = await github.rest.repos.listTags({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 10,
});
// First tag that isn't the current version
const prev = tags.data.find(t => t.name !== tagName);
if (prev) previousTag = prev.name;
} catch (e) {
core.info(`No previous tags found: ${e.message}`);
}

// Generate release notes via GitHub API
const params = {
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: tagName,
};
if (previousTag) params.previous_tag_name = previousTag;

const { data } = await github.rest.repos.generateReleaseNotes(params);
core.setOutput('notes', data.body);
core.setOutput('prev_tag', previousTag || 'none');

- name: Enhance release notes with AI
id: changelog
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${{ needs.validate-release.outputs.version }}"

# Generate changelog entry
if [ -f CHANGELOG.md ]; then
# Extract changelog entry for this version
CHANGELOG_ENTRY=$(sed -n "/## \[$VERSION\]/,/## \[/p" CHANGELOG.md | sed '$d')
if [ -z "$CHANGELOG_ENTRY" ]; then
CHANGELOG_ENTRY="Release $VERSION"
fi
else
CHANGELOG_ENTRY="Release $VERSION"
VERSION="${{ needs.detect-release.outputs.version }}"
GITHUB_NOTES="${{ steps.github_notes.outputs.notes }}"

# Build the prompt — GitHub notes as input, AI rewrites
PROMPT="You are a release notes writer for @genwave/svgmaker-sdk, a Node.js SDK.
Rewrite the following GitHub-generated release notes into polished, user-facing release notes for v${VERSION}.

Rules:
- Use sections: ## What's New, ## Bug Fixes, ## Other Changes (omit empty ones)
- Write concise descriptions a developer would want to read
- If any changes are breaking or require user action, add a ## Migration Guide section
- Remove contributor/new-contributor boilerplate
- Do not wrap in code fences
- Do not include a title/heading for the version itself

GitHub-generated notes:
${GITHUB_NOTES}"

PROMPT_JSON=$(echo "$PROMPT" | jq -Rs .)

RESPONSE=$(curl -sS -L \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "X-GitHub-Api-Version: 2022-11-28" \
-H "Content-Type: application/json" \
https://models.github.ai/inference/chat/completions \
-d "{
\"model\": \"openai/gpt-4.1\",
\"messages\": [
{\"role\": \"user\", \"content\": $PROMPT_JSON}
],
\"temperature\": 0.3
}")

BODY=$(echo "$RESPONSE" | jq -r '.choices[0].message.content // empty')

# Fallback to GitHub's raw notes if AI fails
if [ -z "$BODY" ]; then
echo "AI enhancement failed, using GitHub-generated notes"
echo "API response: $RESPONSE"
BODY="$GITHUB_NOTES"
fi

# Save changelog to output

{
echo 'CHANGELOG_CONTENT<<EOF'
echo "$CHANGELOG_ENTRY"
echo 'body<<EOF'
echo "$BODY"
echo 'EOF'
} >> $GITHUB_OUTPUT
- name: Publish to npm

- name: Create and push git tag
run: |
VERSION="${{ needs.validate-release.outputs.version }}"
echo "📦 Publishing version $VERSION to npm..."
npm publish --provenance

- name: Create GitHub Release
uses: actions/create-release@v1
VERSION="${{ needs.detect-release.outputs.version }}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "v${VERSION}" -m "Release v${VERSION}"
git push origin "v${VERSION}"

- name: Publish to npm
run: npm publish --provenance
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ needs.validate-release.outputs.version }}
release_name: Release ${{ needs.validate-release.outputs.version }}
body: ${{ steps.changelog.outputs.CHANGELOG_CONTENT }}
draft: false
prerelease: ${{ contains(needs.validate-release.outputs.version, '-') }}
tag_name: v${{ needs.detect-release.outputs.version }}
name: v${{ needs.detect-release.outputs.version }}
body: ${{ steps.changelog.outputs.body }}
prerelease: ${{ contains(needs.detect-release.outputs.version, '-') }}
generate_release_notes: false

- name: Notify success
if: success()
run: |
VERSION="${{ needs.validate-release.outputs.version }}"
echo "✅ Successfully released version $VERSION"
echo "📦 Package published to npm: https://www.npmjs.com/package/@genwave/svgmaker-sdk"
echo "🚀 GitHub release created: ${{ github.server_url }}/${{ github.repository }}/releases/tag/v$VERSION"
VERSION="${{ needs.detect-release.outputs.version }}"
echo "Successfully released version $VERSION"
echo "Package published to npm: https://www.npmjs.com/package/@genwave/svgmaker-sdk"
echo "GitHub release created: ${{ github.server_url }}/${{ github.repository }}/releases/tag/v$VERSION"
2 changes: 1 addition & 1 deletion .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
run: npm ci

- name: Run npm audit
run: npm audit --audit-level moderate
run: npm audit --audit-level moderate --omit=dev

- name: Run npm audit fix
if: failure()
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ test-generated-*.svg
test-*.png
test-*.svg

# Test config (contains API keys)
tests/test-config.js

# Test output assets
tests/assets/

# Temporary files
tmp/
temp/
Loading