From 7edf1313190c59d16620adf04ac2adf9b4bd7471 Mon Sep 17 00:00:00 2001 From: Lucki2g Date: Fri, 5 Jun 2026 21:27:39 +0200 Subject: [PATCH 1/2] chore: changed actions so they are not blocked by branch protection. You must now label PR for release --- .github/workflows/release-prepare.yml | 205 ++++++++++++++++++++++++++ .github/workflows/release-publish.yml | 70 +++++++++ .github/workflows/release.yml | 173 ---------------------- 3 files changed, 275 insertions(+), 173 deletions(-) create mode 100644 .github/workflows/release-prepare.yml create mode 100644 .github/workflows/release-publish.yml delete mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release-prepare.yml b/.github/workflows/release-prepare.yml new file mode 100644 index 0000000..b173099 --- /dev/null +++ b/.github/workflows/release-prepare.yml @@ -0,0 +1,205 @@ +name: Release (Prepare) + +# Opt-in release preparation that runs DURING a pull request. +# +# Add one of these labels to a PR to prepare a release: +# release:patch | release:minor | release:major +# +# The workflow bumps Website/package.json + .release-please-manifest.json, +# regenerates the changelog, and commits the result to the PR branch. +# No direct push to main happens here (branch protection is respected) and +# no tag is created. Tagging is handled by release-publish.yml after merge. + +on: + pull_request: + types: [labeled] + +permissions: + contents: write + pull-requests: write + +concurrency: + # Serialize per-PR so re-labeling doesn't race itself. + group: release-prepare-${{ github.event.pull_request.number }} + cancel-in-progress: false + +jobs: + prepare: + runs-on: ubuntu-latest + # Only react to release:* labels. + if: startsWith(github.event.label.name, 'release:') + steps: + - name: Parse release type from label + id: parse + run: | + LABEL="${{ github.event.label.name }}" + TYPE="${LABEL#release:}" + case "$TYPE" in + patch|minor|major) ;; + *) + echo "::error::Unsupported release label '$LABEL'. Use release:patch, release:minor or release:major." + exit 1 + ;; + esac + echo "release_type=$TYPE" >> "$GITHUB_OUTPUT" + + - name: Checkout PR branch + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Skip if release already prepared on this branch + id: guard + run: | + LAST_MSG=$(git log -1 --pretty=format:%s) + if echo "$LAST_MSG" | grep -q '^chore(release):'; then + echo "Release commit already present on branch head — skipping." + echo "skip=true" >> "$GITHUB_OUTPUT" + else + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + + - name: Setup Node.js + if: steps.guard.outputs.skip != 'true' + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Prepare release commit + if: steps.guard.outputs.skip != 'true' + id: prepare + env: + RELEASE_TYPE: ${{ steps.parse.outputs.release_type }} + REPO: ${{ github.repository }} + run: | + npm install semver + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + CURRENT_VERSION=$(jq -r '.Website' .release-please-manifest.json) + echo "Current version: $CURRENT_VERSION" + + NEXT_VERSION=$(node -e " + const semver = require('semver'); + console.log(semver.inc('$CURRENT_VERSION', process.env.RELEASE_TYPE)); + ") + echo "Next version: $NEXT_VERSION" + echo "version=$NEXT_VERSION" >> "$GITHUB_OUTPUT" + + # Bump package + manifest + (cd Website && npm version "$NEXT_VERSION" --no-git-tag-version) + jq --arg version "$NEXT_VERSION" '.Website = $version' .release-please-manifest.json > temp.json && mv temp.json .release-please-manifest.json + + # Generate enhanced changelog from commits since the last release tag + cat > generate_changelog.js << 'EOF' + const { execSync } = require('child_process'); + + function generateChangelog(lastTag, nextVersion, repo) { + let commits = []; + try { + const range = lastTag ? `${lastTag}..HEAD` : 'HEAD'; + const output = execSync(`git log ${range} --pretty=format:"%h|%s"`).toString(); + commits = output.split('\n').filter(line => line.trim()).map(line => { + const idx = line.indexOf('|'); + return { hash: line.slice(0, idx), message: line.slice(idx + 1) }; + }); + } catch (error) { + return `## [${nextVersion}] - ${new Date().toISOString().split('T')[0]}\n\n### Changed\n- Manual release\n\n`; + } + + const categories = { + 'Features': [], + 'Bug Fixes': [], + 'Performance Improvements': [], + 'UI/UX Improvements': [], + 'Code Refactoring': [], + 'Other Changes': [] + }; + + commits.forEach(commit => { + const { hash, message } = commit; + const link = `([${hash}](https://github.com/${repo}/commit/${hash}))`; + let cleanMessage = message + .replace(/^(feat|feature):\s*/i, '') + .replace(/^(fix|bugfix):\s*/i, '') + .replace(/^(perf|performance):\s*/i, '') + .replace(/^(style|ui|ux):\s*/i, '') + .replace(/^(refactor|refact):\s*/i, '') + .replace(/^chore:\s*/i, ''); + + if (message.match(/^feat|feature|add|implement|new/i) || message.includes('PBI')) { + categories['Features'].push(`* ${cleanMessage} ${link}`); + } else if (message.match(/^fix|bug|resolve|correct/i)) { + categories['Bug Fixes'].push(`* ${cleanMessage} ${link}`); + } else if (message.match(/perf|performance|optim|speed|fast/i)) { + categories['Performance Improvements'].push(`* ${cleanMessage} ${link}`); + } else if (message.match(/ui|ux|style|design|visual|appearance/i)) { + categories['UI/UX Improvements'].push(`* ${cleanMessage} ${link}`); + } else if (message.match(/refactor|restructure|reorganize|clean/i)) { + categories['Code Refactoring'].push(`* ${cleanMessage} ${link}`); + } else if (!message.match(/^merge|^chore\(release\)/i)) { + categories['Other Changes'].push(`* ${cleanMessage} ${link}`); + } + }); + + let changelog = `## [${nextVersion}] - ${new Date().toISOString().split('T')[0]}\n\n`; + Object.entries(categories).forEach(([category, items]) => { + if (items.length > 0) { + changelog += `### ${category}\n\n`; + items.forEach(item => { changelog += `${item}\n`; }); + changelog += '\n'; + } + }); + + if (Object.values(categories).every(cat => cat.length === 0)) { + changelog += `### Changed\n\n* Release ${nextVersion}\n\n`; + } + + return changelog; + } + + console.log(generateChangelog(process.argv[2], process.argv[3], process.argv[4])); + EOF + + git fetch --tags + LAST_TAG="website-v$CURRENT_VERSION" + if ! git rev-parse -q --verify "refs/tags/$LAST_TAG" >/dev/null; then + LAST_TAG="" + fi + + node generate_changelog.js "$LAST_TAG" "$NEXT_VERSION" "$REPO" > temp_changelog.md + + if [ -f "Website/CHANGELOG.md" ]; then + cat temp_changelog.md Website/CHANGELOG.md > temp_full_changelog.md + mv temp_full_changelog.md Website/CHANGELOG.md + else + mv temp_changelog.md Website/CHANGELOG.md + fi + + rm -f generate_changelog.js temp_changelog.md + + git add Website/package.json Website/package-lock.json Website/CHANGELOG.md .release-please-manifest.json + git commit -m "chore(release): release $NEXT_VERSION + + Release type: $RELEASE_TYPE + Previous version: $CURRENT_VERSION + New version: $NEXT_VERSION" + + git push origin "HEAD:${{ github.event.pull_request.head.ref }}" + + - name: Comment on PR + if: steps.guard.outputs.skip != 'true' + uses: actions/github-script@v7 + with: + script: | + const version = '${{ steps.prepare.outputs.version }}'; + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body: `🚀 **Release \`v${version}\` prepared** on this branch.\n\nThe version bump and changelog have been committed. When this PR is merged, tag \`website-v${version}\` and a GitHub Release will be created automatically.` + }); diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml new file mode 100644 index 0000000..5242474 --- /dev/null +++ b/.github/workflows/release-publish.yml @@ -0,0 +1,70 @@ +name: Release (Publish) + +# Runs AFTER a release-labeled PR is merged into main. +# +# Detects that the merged PR carried a release:* label, then creates the +# annotated tag (website-vX.Y.Z) and a GitHub Release. The version is read +# from .release-please-manifest.json on main, so it works regardless of +# whether the PR was merged with a merge commit or squashed. + +on: + pull_request: + types: [closed] + +permissions: + contents: write + +jobs: + publish: + runs-on: ubuntu-latest + if: > + github.event.pull_request.merged == true && + contains(join(github.event.pull_request.labels.*.name, ','), 'release:') + steps: + - name: Checkout main + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.base.ref }} + fetch-depth: 0 + + - name: Read version and prepare tag + id: version + run: | + VERSION=$(jq -r '.Website' .release-please-manifest.json) + TAG="website-v$VERSION" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + + git fetch --tags + if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then + echo "Tag $TAG already exists — nothing to publish." + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + + - name: Extract latest changelog section + if: steps.version.outputs.exists != 'true' + id: notes + run: | + # Grab everything from the first "## [" heading up to the next one. + awk '/^## \[/{c++} c==1' Website/CHANGELOG.md > release_notes.md + echo "Release notes:" + cat release_notes.md + + - name: Create tag and GitHub Release + if: steps.version.outputs.exists != 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ steps.version.outputs.tag }} + VERSION: ${{ steps.version.outputs.version }} + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + git tag -a "$TAG" -m "Release $VERSION" + git push origin "refs/tags/$TAG" + + gh release create "$TAG" \ + --title "Release $VERSION" \ + --notes-file release_notes.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index db74f71..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,173 +0,0 @@ -name: Release - -on: - workflow_dispatch: - inputs: - release_type: - description: 'Type of release' - required: true - default: 'patch' - type: choice - options: - - patch - - minor - - major - -jobs: - release-please: - runs-on: ubuntu-latest - if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.merged == true) - outputs: - releases_created: ${{ steps.manual_release.outputs.releases_created }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: 🔧 Setup Node.js (Manual Release) - if: github.event_name == 'workflow_dispatch' - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: 🚀 Create Manual Release Commit - if: github.event_name == 'workflow_dispatch' - id: manual_release - run: | - npm install -g release-please - npm install semver - - git config --global user.name "github-actions[bot]" - git config --global user.email "github-actions[bot]@users.noreply.github.com" - - CURRENT_VERSION=$(cat .release-please-manifest.json | jq -r '.Website') - echo "Current version: $CURRENT_VERSION" - - NEXT_VERSION=$(node -e " - const semver = require('semver'); - const current = '$CURRENT_VERSION'; - const type = '${{ github.event.inputs.release_type }}'; - console.log(semver.inc(current, type)); - ") - - echo "Next version will be: $NEXT_VERSION" - - cd Website - npm version $NEXT_VERSION --no-git-tag-version - cd .. - - jq --arg version "$NEXT_VERSION" '.Website = $version' .release-please-manifest.json > temp.json && mv temp.json .release-please-manifest.json - - # Generate enhanced changelog - cat > generate_changelog.js << 'EOF' - const { execSync } = require('child_process'); - - function generateChangelog(lastTag, nextVersion, repo) { - let commits = []; - try { - const output = execSync(`git log ${lastTag}..HEAD --pretty=format:"%h|%s"`).toString(); - commits = output.split('\n').filter(line => line.trim()).map(line => { - const [hash, message] = line.split('|'); - return { hash, message }; - }); - } catch (error) { - console.log('No commits found or error getting commits'); - return `## [${nextVersion}] - ${new Date().toISOString().split('T')[0]}\n\n### Changed\n- Manual release\n\n`; - } - - const categories = { - 'Features': [], - 'Bug Fixes': [], - 'Performance Improvements': [], - 'UI/UX Improvements': [], - 'Code Refactoring': [], - 'Other Changes': [] - }; - - commits.forEach(commit => { - const { hash, message } = commit; - const link = `([${hash}](https://github.com/${repo}/commit/${hash}))`; - let cleanMessage = message; - - // Clean up common prefixes - cleanMessage = cleanMessage.replace(/^(feat|feature):\s*/i, ''); - cleanMessage = cleanMessage.replace(/^(fix|bugfix):\s*/i, ''); - cleanMessage = cleanMessage.replace(/^(perf|performance):\s*/i, ''); - cleanMessage = cleanMessage.replace(/^(style|ui|ux):\s*/i, ''); - cleanMessage = cleanMessage.replace(/^(refactor|refact):\s*/i, ''); - cleanMessage = cleanMessage.replace(/^chore:\s*/i, ''); - - // Categorize commits - if (message.match(/^feat|feature|add|implement|new/i) || message.includes('PBI')) { - categories['Features'].push(`* ${cleanMessage} ${link}`); - } else if (message.match(/^fix|bug|resolve|correct/i)) { - categories['Bug Fixes'].push(`* ${cleanMessage} ${link}`); - } else if (message.match(/perf|performance|optim|speed|fast/i)) { - categories['Performance Improvements'].push(`* ${cleanMessage} ${link}`); - } else if (message.match(/ui|ux|style|design|visual|appearance/i)) { - categories['UI/UX Improvements'].push(`* ${cleanMessage} ${link}`); - } else if (message.match(/refactor|restructure|reorganize|clean/i)) { - categories['Code Refactoring'].push(`* ${cleanMessage} ${link}`); - } else if (!message.match(/^merge|^chore\(release\)/i)) { - categories['Other Changes'].push(`* ${cleanMessage} ${link}`); - } - }); - - let changelog = `## [${nextVersion}] - ${new Date().toISOString().split('T')[0]}\n\n`; - - Object.entries(categories).forEach(([category, items]) => { - if (items.length > 0) { - changelog += `### ${category}\n\n`; - items.forEach(item => { - changelog += `${item}\n`; - }); - changelog += '\n'; - } - }); - - // If no categorized items, add a simple changed section - if (Object.values(categories).every(cat => cat.length === 0)) { - changelog += `### Changed\n\n* Manual ${nextVersion.includes('major') ? 'major' : nextVersion.includes('minor') ? 'minor' : 'patch'} release\n\n`; - } - - return changelog; - } - - const lastTag = process.argv[2]; - const nextVersion = process.argv[3]; - const repo = process.argv[4]; - console.log(generateChangelog(lastTag, nextVersion, repo)); - EOF - - LAST_TAG="website-v$CURRENT_VERSION" - git fetch --tags - - node generate_changelog.js "$LAST_TAG" "$NEXT_VERSION" "${{ github.repository }}" > temp_changelog.md - - if [ -f "Website/CHANGELOG.md" ]; then - cat temp_changelog.md Website/CHANGELOG.md > temp_full_changelog.md - mv temp_full_changelog.md Website/CHANGELOG.md - else - mv temp_changelog.md Website/CHANGELOG.md - fi - - git add . - git commit -m "chore(release): release $NEXT_VERSION - - Release type: ${{ github.event.inputs.release_type }} - Previous version: $CURRENT_VERSION - New version: $NEXT_VERSION" - - git tag -a "website-v$NEXT_VERSION" -m "Release $NEXT_VERSION" - git push origin HEAD --tags - - echo "releases_created=true" >> $GITHUB_OUTPUT - echo "version=$NEXT_VERSION" >> $GITHUB_OUTPUT - - - name: 📝 Manual Release Summary - if: github.event_name == 'workflow_dispatch' - run: | - echo "✅ Manual release commit created with type: ${{ github.event.inputs.release_type }}" - echo "🏷️ New version: ${{ steps.manual_release.outputs.version }}" - echo "📝 Changes have been committed and pushed to the current branch." - echo "🔗 View the commit: https://github.com/${{ github.repository }}/commit/$(git rev-parse HEAD)" From 7038fd475cd8329c10a894bcc0ff33ea9ef0df9a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 5 Jun 2026 19:31:46 +0000 Subject: [PATCH 2/2] chore(release): release 2.3.5 Release type: patch Previous version: 2.3.4 New version: 2.3.5 --- .release-please-manifest.json | 2 +- Website/CHANGELOG.md | 7 +++++++ Website/package-lock.json | 4 ++-- Website/package.json | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index a2f4917..5dc2a30 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - "Website": "2.3.4" + "Website": "2.3.5" } diff --git a/Website/CHANGELOG.md b/Website/CHANGELOG.md index b109749..8d47d9b 100644 --- a/Website/CHANGELOG.md +++ b/Website/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.3.5] - 2026-06-05 + +### Other Changes + +* changed actions so they are not blocked by branch protection. You must now label PR for release ([7edf131](https://github.com/delegateas/DataModelViewer/commit/7edf131)) + + No commits found or error getting commits ## [2.3.4] - 2026-04-20 diff --git a/Website/package-lock.json b/Website/package-lock.json index 8637563..1521282 100644 --- a/Website/package-lock.json +++ b/Website/package-lock.json @@ -1,12 +1,12 @@ { "name": "website", - "version": "2.3.4", + "version": "2.3.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "website", - "version": "2.3.4", + "version": "2.3.5", "dependencies": { "@azure/identity": "^4.13.0", "@joint/core": "^4.1.3", diff --git a/Website/package.json b/Website/package.json index aee2090..b92f3f7 100644 --- a/Website/package.json +++ b/Website/package.json @@ -1,6 +1,6 @@ { "name": "website", - "version": "2.3.4", + "version": "2.3.5", "private": true, "scripts": { "dev": "next dev",