From 49419559b0360196ae68f67c1036044e45dd961d Mon Sep 17 00:00:00 2001 From: MehmetScgn Date: Wed, 25 Mar 2026 15:05:31 +0200 Subject: [PATCH 1/3] fix: only run ci and pages when needed --- .github/workflows/ci.yml | 2 -- .github/workflows/pages.yml | 45 +++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/pages.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1122164..b0e4be9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,8 +1,6 @@ name: CI on: - push: - branches: [main] pull_request: jobs: diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..cf6f9a2 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,45 @@ +name: Pages + +on: + push: + branches: + - main + paths: + - index.html + - .github/workflows/pages.yml + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + deploy: + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Configure Pages + uses: actions/configure-pages@v5 + + - name: Prepare site + run: | + mkdir -p _site + cp index.html _site/index.html + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: _site + + - name: Deploy Pages + id: deployment + uses: actions/deploy-pages@v4 From 794d0f01c96b158d59f0df381a750b0d25e9eb43 Mon Sep 17 00:00:00 2001 From: MehmetScgn Date: Wed, 25 Mar 2026 15:11:55 +0200 Subject: [PATCH 2/3] fix: base releases on the highest known version --- scripts/release-plan.ts | 34 +++++++++++++++++++++++++++++++--- test/release-plan.test.ts | 6 +++++- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/scripts/release-plan.ts b/scripts/release-plan.ts index 136f01c..d63afdd 100644 --- a/scripts/release-plan.ts +++ b/scripts/release-plan.ts @@ -2,6 +2,7 @@ import { execFileSync } from 'node:child_process'; import fs from 'node:fs'; import process from 'node:process'; import semver from 'semver'; +import packageJson from '../package.json' with { type: 'json' }; export type ReleaseLevel = 'none' | 'patch' | 'minor' | 'major'; @@ -53,8 +54,9 @@ export function headReleaseTag(): string | null { return gitOrNull(['tag', '--points-at', 'HEAD', '--list', 'v*']); } -export function latestReleaseTag(): string | null { - return gitOrNull(['describe', '--tags', '--abbrev=0', '--match', 'v*']); +export function highestReleaseTag(): string | null { + const output = gitOrNull(['tag', '--list', 'v*', '--sort=-version:refname']); + return output?.split('\n')[0] ?? null; } export function normalizeTagVersion(tag: string | null): string { @@ -66,6 +68,15 @@ export function normalizeTagVersion(tag: string | null): string { return version; } +export function resolveBaseVersion(candidates: Array): string { + const versions = candidates + .map((candidate) => normalizeTagVersion(candidate ?? null)) + .filter((value, index, all) => all.indexOf(value) === index) + .sort(semver.rcompare); + + return versions[0] ?? '0.0.0'; +} + export function resolveReleaseLevel(labels: string[]): ReleaseLevel { const matches = labels .map((label) => RELEASE_LABELS[label]) @@ -157,6 +168,21 @@ async function fetchMergedPullRequest(repo: string, sha: string, token: string): return merged; } +function latestPublishedVersion(packageName: string): string | null { + try { + const raw = execFileSync('npm', ['view', packageName, 'version', '--json'], { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'ignore'], + }).trim(); + if (!raw) return null; + + const parsed = JSON.parse(raw) as string; + return semver.valid(parsed) ?? null; + } catch { + return null; + } +} + function writeOutputs(plan: ReleasePlan): void { const outputPath = process.env.GITHUB_OUTPUT; if (!outputPath) return; @@ -187,9 +213,11 @@ async function main(): Promise { const pr = await fetchMergedPullRequest(repo, sha, token); const labels = (pr.labels ?? []).map((label) => label.name).filter((value): value is string => Boolean(value)); + const publishedVersion = latestPublishedVersion(packageJson.name); + const highestTag = highestReleaseTag(); const plan = buildReleasePlan({ headTag: headReleaseTag(), - lastTag: latestReleaseTag(), + lastTag: `v${resolveBaseVersion([highestTag, publishedVersion])}`, labels, prNumber: pr.number, prTitle: pr.title, diff --git a/test/release-plan.test.ts b/test/release-plan.test.ts index e5890dc..ab4efb6 100644 --- a/test/release-plan.test.ts +++ b/test/release-plan.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { buildReleasePlan, normalizeTagVersion, resolveReleaseLevel } from '../scripts/release-plan.ts'; +import { buildReleasePlan, normalizeTagVersion, resolveBaseVersion, resolveReleaseLevel } from '../scripts/release-plan.ts'; describe('release-plan', () => { it('defaults to a patch release when no release label is present', () => { @@ -69,4 +69,8 @@ describe('release-plan', () => { it('normalizes missing tags to 0.0.0', () => { expect(normalizeTagVersion(null)).toBe('0.0.0'); }); + + it('uses the highest known release version as the base', () => { + expect(resolveBaseVersion(['v0.1.13', '0.1.14', 'v0.1.12'])).toBe('0.1.14'); + }); }); From 8f961283f851cf2432bf62f4335c75bf66fcd488 Mon Sep 17 00:00:00 2001 From: MehmetScgn Date: Wed, 25 Mar 2026 16:57:50 +0200 Subject: [PATCH 3/3] fix: use npm as the release version source --- scripts/release-plan.ts | 20 +++++++++++--------- test/release-plan.test.ts | 8 ++++++-- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/scripts/release-plan.ts b/scripts/release-plan.ts index d63afdd..95baa2d 100644 --- a/scripts/release-plan.ts +++ b/scripts/release-plan.ts @@ -2,7 +2,6 @@ import { execFileSync } from 'node:child_process'; import fs from 'node:fs'; import process from 'node:process'; import semver from 'semver'; -import packageJson from '../package.json' with { type: 'json' }; export type ReleaseLevel = 'none' | 'patch' | 'minor' | 'major'; @@ -36,6 +35,7 @@ const RELEASE_LABELS: Record = { 'release:minor': 'minor', 'release:major': 'major', }; +const PACKAGE_NAME = 'dispatchkit'; function git(args: string[]): string { return execFileSync('git', args, { encoding: 'utf8' }).trim(); @@ -68,13 +68,15 @@ export function normalizeTagVersion(tag: string | null): string { return version; } -export function resolveBaseVersion(candidates: Array): string { - const versions = candidates - .map((candidate) => normalizeTagVersion(candidate ?? null)) - .filter((value, index, all) => all.indexOf(value) === index) - .sort(semver.rcompare); +export function resolveBaseVersion(input: { + publishedVersion: string | null; + highestTag: string | null; +}): string { + if (input.publishedVersion) { + return normalizeTagVersion(input.publishedVersion); + } - return versions[0] ?? '0.0.0'; + return normalizeTagVersion(input.highestTag); } export function resolveReleaseLevel(labels: string[]): ReleaseLevel { @@ -213,11 +215,11 @@ async function main(): Promise { const pr = await fetchMergedPullRequest(repo, sha, token); const labels = (pr.labels ?? []).map((label) => label.name).filter((value): value is string => Boolean(value)); - const publishedVersion = latestPublishedVersion(packageJson.name); + const publishedVersion = latestPublishedVersion(PACKAGE_NAME); const highestTag = highestReleaseTag(); const plan = buildReleasePlan({ headTag: headReleaseTag(), - lastTag: `v${resolveBaseVersion([highestTag, publishedVersion])}`, + lastTag: `v${resolveBaseVersion({ publishedVersion, highestTag })}`, labels, prNumber: pr.number, prTitle: pr.title, diff --git a/test/release-plan.test.ts b/test/release-plan.test.ts index ab4efb6..f918cbd 100644 --- a/test/release-plan.test.ts +++ b/test/release-plan.test.ts @@ -70,7 +70,11 @@ describe('release-plan', () => { expect(normalizeTagVersion(null)).toBe('0.0.0'); }); - it('uses the highest known release version as the base', () => { - expect(resolveBaseVersion(['v0.1.13', '0.1.14', 'v0.1.12'])).toBe('0.1.14'); + it('prefers npm as the canonical published version', () => { + expect(resolveBaseVersion({ publishedVersion: '0.1.14', highestTag: 'v0.1.13' })).toBe('0.1.14'); + }); + + it('falls back to the highest tag when the package is not published yet', () => { + expect(resolveBaseVersion({ publishedVersion: null, highestTag: 'v0.1.13' })).toBe('0.1.13'); }); });