Skip to content

Commit 6f7fd8a

Browse files
committed
Update tag/release scripts
1 parent d0b302e commit 6f7fd8a

File tree

6 files changed

+211
-34
lines changed

6 files changed

+211
-34
lines changed

.github/workflows/build.yaml

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
on:
2-
- push
3-
- pull_request
2+
push:
3+
branches:
4+
- '*'
5+
pull_request:
6+
branches:
7+
- '*'
48

59
jobs:
610
build-and-test:
@@ -18,8 +22,8 @@ jobs:
1822
- name: Install Node.js
1923
uses: actions/setup-node@v4
2024
with:
21-
node-version: 20
22-
cache: 'pnpm'
25+
node-version: 22
26+
cache: pnpm
2327

2428
- name: Install dependencies
2529
run: pnpm install

.github/workflows/publish.yaml

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
on:
2+
push:
3+
tags:
4+
- '[a-z]+@[0-9]+.[0-9]+.[0-9]+'
5+
- '[a-z]+-[a-z]+@[0-9]+.[0-9]+.[0-9]+'
6+
- '[a-z]+-[a-z]+-[a-z]+@[0-9]+.[0-9]+.[0-9]+'
7+
8+
jobs:
9+
release:
10+
runs-on: ubuntu-latest
11+
12+
permissions:
13+
contents: write # Required for publishing the GitHub release.
14+
id-token: write # The OIDC ID token is used for authentication with JSR.
15+
16+
steps:
17+
- name: Checkout
18+
uses: actions/checkout@v4
19+
20+
- uses: pnpm/action-setup@v4
21+
name: Install pnpm
22+
with:
23+
run_install: false
24+
25+
- name: Install Node.js
26+
uses: actions/setup-node@v4
27+
with:
28+
node-version: 22
29+
registry-url: https://registry.npmjs.org
30+
cache: pnpm
31+
32+
- name: Install dependencies
33+
run: pnpm install
34+
35+
- name: Publish
36+
run: pnpm publish-release-ci ${{ github.ref_name }}
37+
env:
38+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
39+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

.github/workflows/release.yaml

-29
This file was deleted.

scripts/publish-release.js

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import * as cp from 'node:child_process';
2+
3+
import { createRelease } from './utils/github-releases.js';
4+
import { getPackageDir, hasJsrJson, readJsrJson, readPackageJson } from './utils/packages.js';
5+
import { logAndExec } from './utils/process.js';
6+
import { isValidVersion } from './utils/semver.js';
7+
8+
let packageName = process.argv[2];
9+
let version = process.argv[3];
10+
11+
if (packageName === undefined || (!packageName.includes('@') && version === undefined)) {
12+
console.error(`Usage:
13+
node publish-release.js <packageName> <version>
14+
node publish-release.js <tag>`);
15+
process.exit(1);
16+
}
17+
18+
if (packageName.startsWith('@mjackson/')) {
19+
packageName = packageName.slice('@mjackson/'.length);
20+
}
21+
22+
if (packageName.includes('@')) {
23+
let split = packageName.split('@');
24+
packageName = split[0];
25+
version = split[1];
26+
}
27+
28+
let tag = `${packageName}@${version}`;
29+
30+
if (packageName === '' || !isValidVersion(version)) {
31+
console.error(`Invalid tag: ${tag}`);
32+
process.exit(1);
33+
}
34+
35+
// 1) Ensure git staging area is clean
36+
let status = cp.execSync('git status --porcelain').toString();
37+
if (status !== '') {
38+
console.error('Git staging area is not clean');
39+
process.exit(1);
40+
}
41+
42+
// 2) Ensure we are on the right tag
43+
let currentTags = cp.execSync('git tag --points-at HEAD').toString().trim().split('\n');
44+
if (!currentTags.includes(tag)) {
45+
console.error(`Tag "${tag}" does not point to HEAD`);
46+
process.exit(1);
47+
}
48+
49+
console.log(`Publishing release ${tag} ...`);
50+
console.log();
51+
52+
// 3) Publish to npm
53+
let packageJson = readPackageJson(packageName);
54+
if (packageJson.version !== version) {
55+
console.error(
56+
`Tag does not match package.json version: ${version} !== ${packageJson.version} (${tag})`,
57+
);
58+
process.exit(1);
59+
}
60+
61+
logAndExec(`npm publish --access public`, {
62+
cwd: getPackageDir(packageName),
63+
env: process.env,
64+
});
65+
console.log();
66+
67+
// 4) Publish to jsr (if applicable)
68+
if (hasJsrJson(packageName)) {
69+
let jsrJson = readJsrJson(packageName);
70+
if (jsrJson.version !== version) {
71+
console.error(
72+
`Tag does not match jsr.json version: ${version} !== ${jsrJson.version} (${tag})`,
73+
);
74+
process.exit(1);
75+
}
76+
77+
logAndExec(`pnpm dlx jsr publish`, {
78+
cwd: getPackageDir(packageName),
79+
env: process.env,
80+
});
81+
console.log();
82+
}
83+
84+
// 5) Publish to GitHub Releases
85+
console.log(`Publishing ${tag} on GitHub Releases ...`);
86+
let releaseUrl = await createRelease(packageName, version);
87+
console.log(`Published at: ${releaseUrl}`);
88+
89+
console.log();

scripts/tag-release.js

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import * as cp from 'node:child_process';
2+
3+
import {
4+
getPackageFile,
5+
readChangelog,
6+
writeChangelog,
7+
hasJsrJson,
8+
readJsrJson,
9+
writeJsrJson,
10+
readPackageJson,
11+
writePackageJson,
12+
} from './utils/packages.js';
13+
import { logAndExec } from './utils/process.js';
14+
import { getNextVersion } from './utils/semver.js';
15+
16+
let packageName = process.argv[2];
17+
let releaseType = process.argv[3];
18+
19+
if (packageName === undefined || releaseType === undefined) {
20+
console.error('Usage: node tag-release.js <packageName> <releaseType>');
21+
process.exit(1);
22+
}
23+
24+
if (packageName.startsWith('@mjackson/')) {
25+
packageName = packageName.slice('@mjackson/'.length);
26+
}
27+
28+
let packageJson = readPackageJson(packageName);
29+
let nextVersion = getNextVersion(packageJson.version, releaseType);
30+
let tag = `${packageName}@${nextVersion}`;
31+
32+
// 1) Ensure git staging area is clean
33+
let status = cp.execSync('git status --porcelain').toString();
34+
if (status !== '') {
35+
console.error('Git staging area is not clean');
36+
process.exit(1);
37+
}
38+
39+
console.log(`Tagging release ${tag} ...`);
40+
console.log();
41+
42+
// 2) Update package.json with the new release version
43+
writePackageJson(packageName, { ...packageJson, version: nextVersion });
44+
logAndExec(`git add ${getPackageFile(packageName, 'package.json')}`);
45+
46+
// 4) Update jsr.json (if applicable) with the new release version
47+
if (hasJsrJson(packageName)) {
48+
let jsrJson = readJsrJson(packageName);
49+
writeJsrJson(packageName, { ...jsrJson, version: nextVersion });
50+
logAndExec(`git add ${getPackageFile(packageName, 'jsr.json')}`);
51+
}
52+
53+
// 3) Swap out "## HEAD" in CHANGELOG.md with the new release version + date
54+
let changelog = readChangelog(packageName);
55+
let match = /^## HEAD\n/m.exec(changelog);
56+
if (match) {
57+
let [today] = new Date().toISOString().split('T');
58+
59+
changelog =
60+
changelog.slice(0, match.index) +
61+
`## v${nextVersion} (${today})\n` +
62+
changelog.slice(match.index + match[0].length);
63+
64+
writeChangelog(packageName, changelog);
65+
logAndExec(`git add ${getPackageFile(packageName, 'CHANGELOG.md')}`);
66+
}
67+
68+
// 5) Commit and tag
69+
logAndExec(`git commit -m "Release ${tag}"`);
70+
logAndExec(`git tag ${tag}`);
71+
72+
console.log();

scripts/utils/github-releases.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { getChanges } from './changes.js';
44

55
const token = process.env.GITHUB_TOKEN;
66

7-
/** @type (packageName: string, version: string) => Promise<void> */
7+
/** @type (packageName: string, version: string) => Promise<string> */
88
export async function createRelease(packageName, version) {
99
if (token === undefined) {
1010
console.error('GITHUB_TOKEN environment variable is required to create a release');
@@ -29,4 +29,6 @@ export async function createRelease(packageName, version) {
2929
console.error('Failed to create release:', response);
3030
process.exit(1);
3131
}
32+
33+
return response.data.html_url;
3234
}

0 commit comments

Comments
 (0)