From d9c88ac376d9a261b2cfa26d16ad29ea92985a8c Mon Sep 17 00:00:00 2001 From: ducdmdev Date: Sat, 28 Feb 2026 16:36:30 +0700 Subject: [PATCH 1/3] feat: add CI workflow for tests and version sync --- .github/workflows/ci.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..55a4548 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install jq + run: sudo apt-get install -q -y jq + + - name: Ensure scripts are executable + run: | + chmod +x scripts/*.sh + find tests -name '*.sh' -exec chmod +x {} + + + - name: Run tests + run: bash tests/run-tests.sh + + - name: Check version sync + run: | + V_PKG=$(jq -r .version package.json) + V_PLUGIN=$(jq -r .version .claude-plugin/plugin.json) + V_MKT=$(jq -r '.plugins[0].version' .claude-plugin/marketplace.json) + echo "package.json=$V_PKG plugin.json=$V_PLUGIN marketplace.json=$V_MKT" + if [ "$V_PKG" != "$V_PLUGIN" ] || [ "$V_PKG" != "$V_MKT" ]; then + echo "::error::Version mismatch across files" + exit 1 + fi + echo "All versions in sync: $V_PKG" From febcc53b5e523b9ad8db4e42f61c1e69323dbb10 Mon Sep 17 00:00:00 2001 From: ducdmdev Date: Sat, 28 Feb 2026 17:15:18 +0700 Subject: [PATCH 2/3] feat: add release workflow with auto-changelog --- .github/workflows/release.yml | 144 ++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..eee5c44 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,144 @@ +name: Release + +on: + push: + tags: + - 'v*' + +permissions: + contents: write + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install jq + run: sudo apt-get install -q -y jq + + - name: Ensure scripts are executable + run: | + chmod +x scripts/*.sh + find tests -name '*.sh' -exec chmod +x {} + + + - name: Extract tag version + id: tag + run: | + TAG_VERSION="${GITHUB_REF_NAME#v}" + echo "version=$TAG_VERSION" >> "$GITHUB_OUTPUT" + echo "Tag version: $TAG_VERSION" + + - name: Check versions match tag + run: | + TAG_VERSION="${{ steps.tag.outputs.version }}" + V_PKG=$(jq -r .version package.json) + V_PLUGIN=$(jq -r .version .claude-plugin/plugin.json) + V_MKT=$(jq -r '.plugins[0].version' .claude-plugin/marketplace.json) + echo "tag=$TAG_VERSION package.json=$V_PKG plugin.json=$V_PLUGIN marketplace.json=$V_MKT" + MISMATCH=false + if [ "$V_PKG" != "$TAG_VERSION" ]; then + echo "::error::package.json ($V_PKG) does not match tag ($TAG_VERSION)" + MISMATCH=true + fi + if [ "$V_PLUGIN" != "$TAG_VERSION" ]; then + echo "::error::plugin.json ($V_PLUGIN) does not match tag ($TAG_VERSION)" + MISMATCH=true + fi + if [ "$V_MKT" != "$TAG_VERSION" ]; then + echo "::error::marketplace.json ($V_MKT) does not match tag ($TAG_VERSION)" + MISMATCH=true + fi + if [ "$MISMATCH" = "true" ]; then + exit 1 + fi + echo "All versions match tag: $TAG_VERSION" + + - name: Run tests + run: bash tests/run-tests.sh + + release: + needs: validate + runs-on: ubuntu-latest + steps: + - name: Checkout (full history) + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Generate changelog + id: changelog + run: | + # Find previous tag + PREV_TAG=$(git tag --sort=-v:refname | grep '^v' | sed -n '2p') + if [ -z "$PREV_TAG" ]; then + # First release — use all commits + RANGE="HEAD" + else + RANGE="${PREV_TAG}..HEAD" + fi + + echo "Generating changelog for $RANGE" + + CHANGELOG="" + + # Collect commits by type + FEATS=$(git log "$RANGE" --pretty=format:"%s" --no-merges | grep -E "^feat(\(.+\))?:" | sed 's/^feat\(([^)]*)\)\?: //' | sed 's/^feat: //' || true) + FIXES=$(git log "$RANGE" --pretty=format:"%s" --no-merges | grep -E "^fix(\(.+\))?:" | sed 's/^fix\(([^)]*)\)\?: //' | sed 's/^fix: //' || true) + REFACTORS=$(git log "$RANGE" --pretty=format:"%s" --no-merges | grep -E "^refactor(\(.+\))?:" | sed 's/^refactor\(([^)]*)\)\?: //' | sed 's/^refactor: //' || true) + DOCS=$(git log "$RANGE" --pretty=format:"%s" --no-merges | grep -E "^docs(\(.+\))?:" | sed 's/^docs\(([^)]*)\)\?: //' | sed 's/^docs: //' || true) + CHORES=$(git log "$RANGE" --pretty=format:"%s" --no-merges | grep -E "^chore(\(.+\))?:" | sed 's/^chore\(([^)]*)\)\?: //' | sed 's/^chore: //' || true) + + if [ -n "$FEATS" ]; then + CHANGELOG+=$'\n## Features\n\n' + while IFS= read -r line; do + CHANGELOG+="- $line"$'\n' + done <<< "$FEATS" + fi + + if [ -n "$FIXES" ]; then + CHANGELOG+=$'\n## Fixes\n\n' + while IFS= read -r line; do + CHANGELOG+="- $line"$'\n' + done <<< "$FIXES" + fi + + if [ -n "$REFACTORS" ]; then + CHANGELOG+=$'\n## Refactors\n\n' + while IFS= read -r line; do + CHANGELOG+="- $line"$'\n' + done <<< "$REFACTORS" + fi + + if [ -n "$DOCS" ]; then + CHANGELOG+=$'\n## Documentation\n\n' + while IFS= read -r line; do + CHANGELOG+="- $line"$'\n' + done <<< "$DOCS" + fi + + if [ -n "$CHORES" ]; then + CHANGELOG+=$'\n## Maintenance\n\n' + while IFS= read -r line; do + CHANGELOG+="- $line"$'\n' + done <<< "$CHORES" + fi + + if [ -z "$CHANGELOG" ]; then + CHANGELOG=$'\nNo categorized changes in this release.\n' + fi + + # Write to file (multi-line output is easier via file) + echo "$CHANGELOG" > /tmp/changelog.md + echo "Changelog generated:" + cat /tmp/changelog.md + + - name: Create GitHub Release + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release create "$GITHUB_REF_NAME" \ + --title "$GITHUB_REF_NAME" \ + --notes-file /tmp/changelog.md \ + --latest From 767bf544cdfa1ecd03357ffc278a2138c4dae89a Mon Sep 17 00:00:00 2001 From: ducdmdev Date: Sat, 28 Feb 2026 17:25:07 +0700 Subject: [PATCH 3/3] fix: use portable sed -E in release changelog generation Switch from GNU-specific BRE \? to ERE via sed -E flag for cross-platform compatibility (GNU sed and BSD/macOS sed). --- .github/workflows/release.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index eee5c44..e79dcca 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -84,11 +84,11 @@ jobs: CHANGELOG="" # Collect commits by type - FEATS=$(git log "$RANGE" --pretty=format:"%s" --no-merges | grep -E "^feat(\(.+\))?:" | sed 's/^feat\(([^)]*)\)\?: //' | sed 's/^feat: //' || true) - FIXES=$(git log "$RANGE" --pretty=format:"%s" --no-merges | grep -E "^fix(\(.+\))?:" | sed 's/^fix\(([^)]*)\)\?: //' | sed 's/^fix: //' || true) - REFACTORS=$(git log "$RANGE" --pretty=format:"%s" --no-merges | grep -E "^refactor(\(.+\))?:" | sed 's/^refactor\(([^)]*)\)\?: //' | sed 's/^refactor: //' || true) - DOCS=$(git log "$RANGE" --pretty=format:"%s" --no-merges | grep -E "^docs(\(.+\))?:" | sed 's/^docs\(([^)]*)\)\?: //' | sed 's/^docs: //' || true) - CHORES=$(git log "$RANGE" --pretty=format:"%s" --no-merges | grep -E "^chore(\(.+\))?:" | sed 's/^chore\(([^)]*)\)\?: //' | sed 's/^chore: //' || true) + FEATS=$(git log "$RANGE" --pretty=format:"%s" --no-merges | grep -E "^feat(\(.+\))?:" | sed -E 's/^feat(\([^)]*\))?: //' || true) + FIXES=$(git log "$RANGE" --pretty=format:"%s" --no-merges | grep -E "^fix(\(.+\))?:" | sed -E 's/^fix(\([^)]*\))?: //' || true) + REFACTORS=$(git log "$RANGE" --pretty=format:"%s" --no-merges | grep -E "^refactor(\(.+\))?:" | sed -E 's/^refactor(\([^)]*\))?: //' || true) + DOCS=$(git log "$RANGE" --pretty=format:"%s" --no-merges | grep -E "^docs(\(.+\))?:" | sed -E 's/^docs(\([^)]*\))?: //' || true) + CHORES=$(git log "$RANGE" --pretty=format:"%s" --no-merges | grep -E "^chore(\(.+\))?:" | sed -E 's/^chore(\([^)]*\))?: //' || true) if [ -n "$FEATS" ]; then CHANGELOG+=$'\n## Features\n\n'