From fbbd35be9ccb65457842cf89907e6e34fe7148a8 Mon Sep 17 00:00:00 2001 From: MrCoder Date: Sat, 28 Mar 2026 19:50:26 +1100 Subject: [PATCH] ci: unify CI and publish into single CD workflow with auto version bump Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/cd.yml | 112 ++++++++++++++++++++++++++++++ .github/workflows/ci.yml | 36 ---------- .github/workflows/npm-publish.yml | 79 --------------------- pnpm-lock.yaml | 38 +++++----- scripts/bump-version.js | 112 ++++++++++++++++++++++++++++++ 5 files changed, 244 insertions(+), 133 deletions(-) create mode 100644 .github/workflows/cd.yml delete mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/npm-publish.yml create mode 100755 scripts/bump-version.js diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 0000000..60c2401 --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,112 @@ +name: Build, Test, and npm Publish + +on: + push: + paths-ignore: + - "docs/**" + - "*.md" + pull_request: + paths-ignore: + - "docs/**" + - "*.md" + +permissions: + contents: write + id-token: write + +jobs: + test: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 8 + run_install: false + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + registry-url: 'https://registry.npmjs.org/' + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install + + - name: Lint + run: pnpm lint + + - name: Test + run: pnpm test + + - name: Build + run: pnpm build + + npm-publish: + runs-on: ubuntu-22.04 + needs: test + permissions: + contents: write + id-token: write + if: github.ref == 'refs/heads/main' + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 8 + run_install: false + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + registry-url: 'https://registry.npmjs.org/' + cache: 'pnpm' + + - name: Upgrade npm for OIDC support + run: npm install -g npm@latest + + - name: Install dependencies + run: pnpm install + + - name: Bump Version + id: bump + run: node scripts/bump-version.js + + - name: Commit version bump + run: | + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git add package.json + git commit -m "chore: bump version to ${{ steps.bump.outputs.version }} [skip ci]" + + - name: Build + run: pnpm build + + - name: Publish to npm with provenance + run: pnpm publish --provenance --access public --no-git-checks + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Create git tag and push + run: | + git tag "v${{ steps.bump.outputs.version }}" + git push origin main --follow-tags + + - name: Add version to job summary + run: | + echo "# Published Version" >> $GITHUB_STEP_SUMMARY + echo "| Package | Version | Tag |" >> $GITHUB_STEP_SUMMARY + echo "| --- | --- | --- |" >> $GITHUB_STEP_SUMMARY + echo "| @zenuml/codemirror-extensions | v${{ steps.bump.outputs.version }} | [v${{ steps.bump.outputs.version }}](https://github.com/ZenUml/codemirror-extensions/releases/tag/v${{ steps.bump.outputs.version }}) |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "[View on npm](https://www.npmjs.com/package/@zenuml/codemirror-extensions/v/${{ steps.bump.outputs.version }})" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 20c6fd6..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: CI - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - lint-and-test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Setup pnpm - uses: pnpm/action-setup@v2 - with: - version: 8 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'pnpm' - - - name: Install dependencies - run: pnpm install - - - name: Run linting - run: pnpm lint - - - name: Run tests - run: pnpm test - - - name: Build package - run: pnpm build \ No newline at end of file diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml deleted file mode 100644 index 37aea95..0000000 --- a/.github/workflows/npm-publish.yml +++ /dev/null @@ -1,79 +0,0 @@ -name: Publish Package to npmjs - -on: - release: - types: [created] - workflow_dispatch: - inputs: - version: - description: 'Version to publish (e.g., patch, minor, major, or explicit version)' - required: true - default: 'patch' - type: choice - options: - - patch - - minor - - major - - prepatch - - preminor - - premajor - - prerelease - -jobs: - build-and-publish: - runs-on: ubuntu-22.04 - permissions: - contents: write # for git tag push - id-token: write # for npm OIDC trusted publishing - steps: - - 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: Install pnpm - uses: pnpm/action-setup@v2 - with: - version: 8 - run_install: false - - - name: Install dependencies - run: pnpm install - - - name: Lint and Test - run: | - pnpm lint - pnpm test - - - name: Build package - run: pnpm build - - - name: Version bump from input (manual trigger) - if: github.event_name == 'workflow_dispatch' - run: | - git config --global user.name "GitHub Actions Bot" - git config --global user.email "actions@github.com" - npm version ${{ github.event.inputs.version }} -m "Bump version to %s [skip ci]" - echo "NEW_VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_ENV - - - name: Extract version from release (release trigger) - if: github.event_name == 'release' - run: | - VERSION=${GITHUB_REF#refs/tags/} - VERSION=${VERSION#v} - echo "NEW_VERSION=$VERSION" >> $GITHUB_ENV - - - name: Upgrade npm for OIDC support - run: npm install -g npm@latest - - - name: Publish to npm with provenance - run: pnpm publish --provenance --access public --no-git-checks - - - name: Push version changes - if: github.event_name == 'workflow_dispatch' - run: git push --follow-tags \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6f1d903..512fee2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,8 +18,8 @@ importers: specifier: ^1.4.0 version: 1.4.2 '@zenuml/core': - specifier: 3.43.3 - version: 3.43.3(yaml@2.7.1) + specifier: ^3.43.3 + version: 3.46.8(yaml@2.7.1) thememirror: specifier: ^2.0.1 version: 2.0.1(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.5) @@ -127,24 +127,28 @@ packages: engines: {node: '>=14.*'} cpu: [arm64] os: [linux] + libc: [musl] '@biomejs/cli-linux-arm64@1.6.3': resolution: {integrity: sha512-wFVkQw38kOssfnkbpSh6ums5TaElw3RAt5i/VZwHmgR2nQgE0fHXLO7HwIE9VBkOEdbiIFq+2PxvFIHuJF3z3Q==} engines: {node: '>=14.*'} cpu: [arm64] os: [linux] + libc: [glibc] '@biomejs/cli-linux-x64-musl@1.6.3': resolution: {integrity: sha512-GelAvGsUwbxfFpKLG+7+dvDmbrfkGqn08sL8CMQrGnhjE1krAqHWiXQsjfmi0UMFdMsk7hbc4oSAP+1+mrXcHQ==} engines: {node: '>=14.*'} cpu: [x64] os: [linux] + libc: [musl] '@biomejs/cli-linux-x64@1.6.3': resolution: {integrity: sha512-vyn8TQaTZg617hjqFitwGmb1St5XXvq6I3vmxU/QFalM74BryMSvYCrYWb2Yw/TkykdEwZTMGYp+SWHRb04fTg==} engines: {node: '>=14.*'} cpu: [x64] os: [linux] + libc: [glibc] '@biomejs/cli-win32-arm64@1.6.3': resolution: {integrity: sha512-Gx8N2Tixke6pAI1BniteCVZgUUmaFEDYosdWxoaCus15BZI/7RcBxhsRM0ZL/lC66StSQ8vHl8JBrrld1k570Q==} @@ -645,56 +649,67 @@ packages: resolution: {integrity: sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.40.0': resolution: {integrity: sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.40.0': resolution: {integrity: sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.40.0': resolution: {integrity: sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loongarch64-gnu@4.40.0': resolution: {integrity: sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-powerpc64le-gnu@4.40.0': resolution: {integrity: sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.40.0': resolution: {integrity: sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.40.0': resolution: {integrity: sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.40.0': resolution: {integrity: sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.40.0': resolution: {integrity: sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.40.0': resolution: {integrity: sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.40.0': resolution: {integrity: sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==} @@ -801,8 +816,8 @@ packages: '@vue/shared@3.5.13': resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==} - '@zenuml/core@3.43.3': - resolution: {integrity: sha512-GcjQ73YAR/NAKIJDC3NAueQQ/5EbJOYVpnHjpVIXaLPqs8b046dqwV8q/7q2+E4EIfZGKER2HvC3BRc3JprQ7Q==} + '@zenuml/core@3.46.8': + resolution: {integrity: sha512-YPjMukTRP2XdZZsBGveOlxOd6NqFuzbFB18OuWD6gwcyejoNBgCDS/ctNjRugTe/apMxvHmtVtiF3spkk3VGLQ==} engines: {node: '>=20'} abort-controller@3.0.0: @@ -1490,13 +1505,6 @@ packages: quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} - radash@12.1.1: - resolution: {integrity: sha512-h36JMxKRqrAxVD8201FrCpyeNuUY9Y5zZwujr20fFO77tpUtGa6EZzfKw/3WaiBX95fq7+MpsuMLNdSnORAwSA==} - engines: {node: '>=14.18.0'} - - ramda@0.28.0: - resolution: {integrity: sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==} - react-dom@19.2.3: resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} peerDependencies: @@ -2544,7 +2552,7 @@ snapshots: '@vue/shared@3.5.13': {} - '@zenuml/core@3.43.3(yaml@2.7.1)': + '@zenuml/core@3.46.8(yaml@2.7.1)': dependencies: '@floating-ui/react': 0.27.16(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@headlessui/react': 2.2.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -2562,8 +2570,6 @@ snapshots: marked: 4.3.0 pako: 2.1.0 pino: 8.21.0 - radash: 12.1.1 - ramda: 0.28.0 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) tailwind-merge: 3.4.0 @@ -3220,10 +3226,6 @@ snapshots: quick-format-unescaped@4.0.4: {} - radash@12.1.1: {} - - ramda@0.28.0: {} - react-dom@19.2.3(react@19.2.3): dependencies: react: 19.2.3 diff --git a/scripts/bump-version.js b/scripts/bump-version.js new file mode 100755 index 0000000..a61725f --- /dev/null +++ b/scripts/bump-version.js @@ -0,0 +1,112 @@ +#!/usr/bin/env node +const fs = require("fs"); +const { execSync } = require("child_process"); +const path = require("path"); + +const getCurrentVersion = () => { + try { + const pkg = require("../package.json"); + const npmVersion = execSync(`npm view ${pkg.name} version`, { + stdio: ["pipe", "pipe", "pipe"], + }) + .toString() + .trim(); + console.log(`Latest version on npm: ${npmVersion}`); + return npmVersion; + } catch (error) { + console.error( + "Failed to get npm version. Please ensure you have npm registry access.", + ); + console.error("Error:", error.message); + process.exit(1); + } +}; + +const getCommitMessages = (currentVersion) => { + try { + try { + execSync("git fetch --tags"); + } catch (e) { + console.log("Warning: Could not fetch tags:", e.message); + } + + const messages = execSync( + `git log v${currentVersion}..HEAD --pretty=format:%s%n%b`, + ).toString(); + console.log("\nCommit messages since", `v${currentVersion}:`); + console.log(messages || "(no commits)"); + return messages; + } catch (e) { + console.log( + "Warning: Could not get commits since last version, falling back to recent commits", + ); + const messages = execSync("git log -10 --pretty=format:%s%n%b").toString(); + console.log("\nRecent commit messages:"); + console.log(messages || "(no commits)"); + return messages; + } +}; + +const determineVersionBump = (messages) => { + const lines = messages.split("\n").filter(Boolean); + if ( + lines.some((msg) => msg.includes("BREAKING CHANGE") || msg.includes("!:")) + ) { + return "major"; + } + if (lines.some((msg) => msg.toLowerCase().startsWith("feat"))) { + return "minor"; + } + return "patch"; +}; + +const getNewVersion = (currentVersion, bump) => { + const [major, minor, patch] = currentVersion.split(".").map(Number); + switch (bump) { + case "major": + return `${major + 1}.0.0`; + case "minor": + return `${major}.${minor + 1}.0`; + case "patch": + return `${major}.${minor}.${patch + 1}`; + default: + throw new Error(`Invalid version bump type: ${bump}`); + } +}; + +const run = async () => { + const dryRun = process.argv.includes("--dry-run"); + if (dryRun) { + console.log("DRY RUN: No changes will be made\n"); + } + + const currentVersion = getCurrentVersion(); + console.log(`Current version: ${currentVersion}`); + + const messages = getCommitMessages(currentVersion); + const versionBump = determineVersionBump(messages); + console.log(`\nDetermined version bump: ${versionBump}`); + + const newVersion = getNewVersion(currentVersion, versionBump); + console.log(`New version will be: ${newVersion}`); + + if (!dryRun) { + const pkg = require("../package.json"); + pkg.version = newVersion; + fs.writeFileSync( + path.join(__dirname, "../package.json"), + JSON.stringify(pkg, null, 2) + "\n", + ); + + if (process.env.GITHUB_ACTIONS) { + fs.appendFileSync(process.env.GITHUB_OUTPUT, `version=${newVersion}\n`); + } + } else { + console.log("\nDRY RUN: No changes were made"); + } +}; + +run().catch((err) => { + console.error("Error:", err.message); + process.exit(1); +});