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 diff --git a/scripts/release-plan.ts b/scripts/release-plan.ts index 136f01c..95baa2d 100644 --- a/scripts/release-plan.ts +++ b/scripts/release-plan.ts @@ -35,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(); @@ -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,17 @@ export function normalizeTagVersion(tag: string | null): string { return version; } +export function resolveBaseVersion(input: { + publishedVersion: string | null; + highestTag: string | null; +}): string { + if (input.publishedVersion) { + return normalizeTagVersion(input.publishedVersion); + } + + return normalizeTagVersion(input.highestTag); +} + export function resolveReleaseLevel(labels: string[]): ReleaseLevel { const matches = labels .map((label) => RELEASE_LABELS[label]) @@ -157,6 +170,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 +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(PACKAGE_NAME); + const highestTag = highestReleaseTag(); const plan = buildReleasePlan({ headTag: headReleaseTag(), - lastTag: latestReleaseTag(), + 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 e5890dc..f918cbd 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,12 @@ describe('release-plan', () => { it('normalizes missing tags to 0.0.0', () => { expect(normalizeTagVersion(null)).toBe('0.0.0'); }); + + 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'); + }); });