Skip to content

Commit

Permalink
Update tag/release scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
mjackson committed Nov 27, 2024
1 parent d0b302e commit 6f7fd8a
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 34 deletions.
12 changes: 8 additions & 4 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
on:
- push
- pull_request
push:
branches:
- '*'
pull_request:
branches:
- '*'

jobs:
build-and-test:
Expand All @@ -18,8 +22,8 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
node-version: 22
cache: pnpm

- name: Install dependencies
run: pnpm install
Expand Down
39 changes: 39 additions & 0 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
on:
push:
tags:
- '[a-z]+@[0-9]+.[0-9]+.[0-9]+'
- '[a-z]+-[a-z]+@[0-9]+.[0-9]+.[0-9]+'
- '[a-z]+-[a-z]+-[a-z]+@[0-9]+.[0-9]+.[0-9]+'

jobs:
release:
runs-on: ubuntu-latest

permissions:
contents: write # Required for publishing the GitHub release.
id-token: write # The OIDC ID token is used for authentication with JSR.

steps:
- name: Checkout
uses: actions/checkout@v4

- uses: pnpm/action-setup@v4
name: Install pnpm
with:
run_install: false

- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 22
registry-url: https://registry.npmjs.org
cache: pnpm

- name: Install dependencies
run: pnpm install

- name: Publish
run: pnpm publish-release-ci ${{ github.ref_name }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
29 changes: 0 additions & 29 deletions .github/workflows/release.yaml

This file was deleted.

89 changes: 89 additions & 0 deletions scripts/publish-release.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import * as cp from 'node:child_process';

import { createRelease } from './utils/github-releases.js';
import { getPackageDir, hasJsrJson, readJsrJson, readPackageJson } from './utils/packages.js';
import { logAndExec } from './utils/process.js';
import { isValidVersion } from './utils/semver.js';

let packageName = process.argv[2];
let version = process.argv[3];

if (packageName === undefined || (!packageName.includes('@') && version === undefined)) {
console.error(`Usage:
node publish-release.js <packageName> <version>
node publish-release.js <tag>`);
process.exit(1);
}

if (packageName.startsWith('@mjackson/')) {
packageName = packageName.slice('@mjackson/'.length);
}

if (packageName.includes('@')) {
let split = packageName.split('@');
packageName = split[0];
version = split[1];
}

let tag = `${packageName}@${version}`;

if (packageName === '' || !isValidVersion(version)) {
console.error(`Invalid tag: ${tag}`);
process.exit(1);
}

// 1) Ensure git staging area is clean
let status = cp.execSync('git status --porcelain').toString();
if (status !== '') {
console.error('Git staging area is not clean');
process.exit(1);
}

// 2) Ensure we are on the right tag
let currentTags = cp.execSync('git tag --points-at HEAD').toString().trim().split('\n');
if (!currentTags.includes(tag)) {
console.error(`Tag "${tag}" does not point to HEAD`);
process.exit(1);
}

console.log(`Publishing release ${tag} ...`);
console.log();

// 3) Publish to npm
let packageJson = readPackageJson(packageName);
if (packageJson.version !== version) {
console.error(
`Tag does not match package.json version: ${version} !== ${packageJson.version} (${tag})`,
);
process.exit(1);
}

logAndExec(`npm publish --access public`, {
cwd: getPackageDir(packageName),
env: process.env,
});
console.log();

// 4) Publish to jsr (if applicable)
if (hasJsrJson(packageName)) {
let jsrJson = readJsrJson(packageName);
if (jsrJson.version !== version) {
console.error(
`Tag does not match jsr.json version: ${version} !== ${jsrJson.version} (${tag})`,
);
process.exit(1);
}

logAndExec(`pnpm dlx jsr publish`, {
cwd: getPackageDir(packageName),
env: process.env,
});
console.log();
}

// 5) Publish to GitHub Releases
console.log(`Publishing ${tag} on GitHub Releases ...`);
let releaseUrl = await createRelease(packageName, version);
console.log(`Published at: ${releaseUrl}`);

console.log();
72 changes: 72 additions & 0 deletions scripts/tag-release.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import * as cp from 'node:child_process';

import {
getPackageFile,
readChangelog,
writeChangelog,
hasJsrJson,
readJsrJson,
writeJsrJson,
readPackageJson,
writePackageJson,
} from './utils/packages.js';
import { logAndExec } from './utils/process.js';
import { getNextVersion } from './utils/semver.js';

let packageName = process.argv[2];
let releaseType = process.argv[3];

if (packageName === undefined || releaseType === undefined) {
console.error('Usage: node tag-release.js <packageName> <releaseType>');
process.exit(1);
}

if (packageName.startsWith('@mjackson/')) {
packageName = packageName.slice('@mjackson/'.length);
}

let packageJson = readPackageJson(packageName);
let nextVersion = getNextVersion(packageJson.version, releaseType);
let tag = `${packageName}@${nextVersion}`;

// 1) Ensure git staging area is clean
let status = cp.execSync('git status --porcelain').toString();
if (status !== '') {
console.error('Git staging area is not clean');
process.exit(1);
}

console.log(`Tagging release ${tag} ...`);
console.log();

// 2) Update package.json with the new release version
writePackageJson(packageName, { ...packageJson, version: nextVersion });
logAndExec(`git add ${getPackageFile(packageName, 'package.json')}`);

// 4) Update jsr.json (if applicable) with the new release version
if (hasJsrJson(packageName)) {
let jsrJson = readJsrJson(packageName);
writeJsrJson(packageName, { ...jsrJson, version: nextVersion });
logAndExec(`git add ${getPackageFile(packageName, 'jsr.json')}`);
}

// 3) Swap out "## HEAD" in CHANGELOG.md with the new release version + date
let changelog = readChangelog(packageName);
let match = /^## HEAD\n/m.exec(changelog);
if (match) {
let [today] = new Date().toISOString().split('T');

changelog =
changelog.slice(0, match.index) +
`## v${nextVersion} (${today})\n` +
changelog.slice(match.index + match[0].length);

writeChangelog(packageName, changelog);
logAndExec(`git add ${getPackageFile(packageName, 'CHANGELOG.md')}`);
}

// 5) Commit and tag
logAndExec(`git commit -m "Release ${tag}"`);
logAndExec(`git tag ${tag}`);

console.log();
4 changes: 3 additions & 1 deletion scripts/utils/github-releases.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { getChanges } from './changes.js';

const token = process.env.GITHUB_TOKEN;

/** @type (packageName: string, version: string) => Promise<void> */
/** @type (packageName: string, version: string) => Promise<string> */
export async function createRelease(packageName, version) {
if (token === undefined) {
console.error('GITHUB_TOKEN environment variable is required to create a release');
Expand All @@ -29,4 +29,6 @@ export async function createRelease(packageName, version) {
console.error('Failed to create release:', response);
process.exit(1);
}

return response.data.html_url;
}

0 comments on commit 6f7fd8a

Please sign in to comment.