Skip to content

Commit d54af08

Browse files
committed
chore: add release script (pnpm release patch|minor|major|x.y.z)
scripts/release.mjs: - Accepts patch/minor/major bump or explicit semver string - Canonical base = MAX version across all publishable packages (prevents accidentally downgrading a package that's already ahead) - Bumps version in all 12 publishable package.json files + root - Commits 'chore: release vX.Y.Z', tags, and pushes to origin - Supports --dry-run to preview without writing - Triggers the GitHub Actions release workflow via the pushed tag Usage: pnpm release patch [--dry-run]
1 parent e3b7b1e commit d54af08

File tree

2 files changed

+150
-1
lines changed

2 files changed

+150
-1
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
"check": "biome check --reporter=summary packages",
1717
"devkit:build": "pnpm --filter conflux-devkit build",
1818
"devkit:start": "node devtools/devkit/dist/cli.js",
19-
"devkit:dev": "pnpm --filter conflux-devkit dev:server"
19+
"devkit:dev": "pnpm --filter conflux-devkit dev:server",
20+
"release": "node scripts/release.mjs"
2021
},
2122
"devDependencies": {
2223
"@biomejs/biome": "^2.0.0",

scripts/release.mjs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Release script — bumps all publishable package versions, commits, tags, and pushes.
4+
*
5+
* Usage:
6+
* pnpm release patch # 0.1.0 → 0.1.1
7+
* pnpm release minor # 0.1.0 → 0.2.0
8+
* pnpm release major # 0.1.0 → 1.0.0
9+
* pnpm release 1.2.3 # set exact version
10+
* pnpm release patch --dry-run # preview without writing anything
11+
*
12+
* What it does:
13+
* 1. Computes the new version from the canonical version (packages/core)
14+
* 2. Rewrites the "version" field in every publishable package.json
15+
* 3. Rewrites the root package.json version to match
16+
* 4. git commit "chore: release vX.Y.Z"
17+
* 5. git tag vX.Y.Z
18+
* 6. git push origin HEAD + tag (triggers the GitHub Actions release workflow)
19+
*/
20+
21+
import { execSync } from 'node:child_process';
22+
import { readFileSync, writeFileSync } from 'node:fs';
23+
import { resolve, dirname } from 'node:path';
24+
import { fileURLToPath } from 'node:url';
25+
26+
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
27+
28+
// ── Publishable packages (all get the same version) ──────────────────────────
29+
const PUBLISHABLE = [
30+
'packages/compiler',
31+
'packages/contracts',
32+
'packages/core',
33+
'packages/defi-react',
34+
'packages/devnode',
35+
'packages/executor',
36+
'packages/protocol',
37+
'packages/react',
38+
'packages/services',
39+
'packages/wallet',
40+
'packages/wallet-connect',
41+
'devtools/devkit',
42+
];
43+
44+
// ── Helpers ───────────────────────────────────────────────────────────────────
45+
46+
function readJson(relPath) {
47+
return JSON.parse(readFileSync(resolve(ROOT, relPath), 'utf8'));
48+
}
49+
50+
function writeJson(relPath, data) {
51+
// Preserve trailing newline and 2-space indent (matches existing files)
52+
writeFileSync(resolve(ROOT, relPath), JSON.stringify(data, null, 2) + '\n', 'utf8');
53+
}
54+
55+
function bumpVersion(current, type) {
56+
const [major, minor, patch] = current.split('.').map(Number);
57+
switch (type) {
58+
case 'major': return `${major + 1}.0.0`;
59+
case 'minor': return `${major}.${minor + 1}.0`;
60+
case 'patch': return `${major}.${minor}.${patch + 1}`;
61+
default: return null; // not a bump type — might be an explicit version
62+
}
63+
}
64+
65+
function isValidSemver(v) {
66+
return /^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?(\+[a-zA-Z0-9.]+)?$/.test(v);
67+
}
68+
69+
function run(cmd, dryRun = false) {
70+
if (dryRun) {
71+
console.log(`[dry-run] ${cmd}`);
72+
return;
73+
}
74+
execSync(cmd, { cwd: ROOT, stdio: 'inherit' });
75+
}
76+
77+
// ── Main ──────────────────────────────────────────────────────────────────────
78+
79+
const args = process.argv.slice(2);
80+
const dryRun = args.includes('--dry-run');
81+
const input = args.find((a) => !a.startsWith('--'));
82+
83+
if (!input) {
84+
console.error('Usage: pnpm release <patch|minor|major|x.y.z> [--dry-run]');
85+
process.exit(1);
86+
}
87+
88+
// Canonical version = highest version across all publishable packages
89+
// (avoids accidentally "downgrading" a package that's already ahead)
90+
const versions = PUBLISHABLE.map((d) => readJson(`${d}/package.json`).version);
91+
function semverCompare(a, b) {
92+
const pa = a.split('.').map(Number);
93+
const pb = b.split('.').map(Number);
94+
for (let i = 0; i < 3; i++) {
95+
if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) > (pb[i] ?? 0) ? 1 : -1;
96+
}
97+
return 0;
98+
}
99+
const currentVersion = [...versions].sort(semverCompare).at(-1);
100+
101+
let newVersion = bumpVersion(currentVersion, input);
102+
if (!newVersion) {
103+
// Not a bump keyword — treat as explicit version
104+
newVersion = input;
105+
}
106+
107+
if (!isValidSemver(newVersion)) {
108+
console.error(`Invalid version: "${newVersion}". Use patch/minor/major or a semver string.`);
109+
process.exit(1);
110+
}
111+
112+
if (newVersion === currentVersion) {
113+
console.error(`New version (${newVersion}) is the same as current (${currentVersion}). Nothing to do.`);
114+
process.exit(1);
115+
}
116+
117+
console.log(`\nBumping: ${currentVersion}${newVersion}${dryRun ? ' [dry-run]' : ''}\n`);
118+
119+
// 1. Update all publishable package.json files
120+
for (const pkgDir of PUBLISHABLE) {
121+
const pkgPath = `${pkgDir}/package.json`;
122+
const pkg = readJson(pkgPath);
123+
const prev = pkg.version;
124+
pkg.version = newVersion;
125+
if (!dryRun) writeJson(pkgPath, pkg);
126+
console.log(` ${pkg.name.padEnd(35)} ${prev}${newVersion}`);
127+
}
128+
129+
// 2. Update root package.json (keeps monorepo in lockstep)
130+
const rootPkg = readJson('package.json');
131+
const rootPrev = rootPkg.version;
132+
rootPkg.version = newVersion;
133+
if (!dryRun) writeJson('package.json', rootPkg);
134+
console.log(` ${'(root monorepo)'.padEnd(35)} ${rootPrev}${newVersion}`);
135+
136+
console.log('');
137+
138+
// 3. Commit, tag, push
139+
const tag = `v${newVersion}`;
140+
const commitMsg = `chore: release ${tag}`;
141+
142+
run(`git add ${PUBLISHABLE.map((p) => `${p}/package.json`).join(' ')} package.json`, dryRun);
143+
run(`git commit -m "${commitMsg}"`, dryRun);
144+
run(`git tag ${tag}`, dryRun);
145+
run('git push origin HEAD', dryRun);
146+
run(`git push origin ${tag}`, dryRun);
147+
148+
console.log(`\n✓ ${tag} released — GitHub Actions will publish to npm\n`);

0 commit comments

Comments
 (0)