|
| 1 | +#!/usr/bin/env node |
| 2 | +// Increment bd-console's stable SemVer without npm's implicit git tag/commit |
| 3 | +// behavior. This is used by the version-bump workflow and is also available to |
| 4 | +// maintainers as `npm run version:bump -- major|minor|patch`. |
| 5 | + |
| 6 | +import { readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from 'node:fs'; |
| 7 | +import { dirname, resolve } from 'node:path'; |
| 8 | +import { fileURLToPath, pathToFileURL } from 'node:url'; |
| 9 | + |
| 10 | +const STABLE_SEMVER_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/; |
| 11 | +const RELEASES = new Set(['major', 'minor', 'patch']); |
| 12 | + |
| 13 | +export function nextVersion(version, release = 'patch') { |
| 14 | + const match = String(version || '').match(STABLE_SEMVER_RE); |
| 15 | + if (!match) throw new Error(`expected a stable SemVer version, got ${JSON.stringify(version)}`); |
| 16 | + if (!RELEASES.has(release)) throw new Error(`release must be major, minor, or patch; got ${JSON.stringify(release)}`); |
| 17 | + |
| 18 | + let major = Number(match[1]); |
| 19 | + let minor = Number(match[2]); |
| 20 | + let patch = Number(match[3]); |
| 21 | + if (release === 'major') { major += 1; minor = 0; patch = 0; } |
| 22 | + if (release === 'minor') { minor += 1; patch = 0; } |
| 23 | + if (release === 'patch') patch += 1; |
| 24 | + return `${major}.${minor}.${patch}`; |
| 25 | +} |
| 26 | + |
| 27 | +export function bumpPackageText(text, release = 'patch') { |
| 28 | + let pkg; |
| 29 | + try { pkg = JSON.parse(text); } catch (err) { throw new Error(`package.json is invalid JSON: ${err.message}`); } |
| 30 | + const current = pkg?.version; |
| 31 | + const next = nextVersion(current, release); |
| 32 | + const escaped = current.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); |
| 33 | + const versionField = new RegExp(`("version"\\s*:\\s*)"${escaped}"`); |
| 34 | + if (!versionField.test(text)) throw new Error('could not locate package.json version field'); |
| 35 | + return { current, next, text: text.replace(versionField, `$1"${next}"`) }; |
| 36 | +} |
| 37 | + |
| 38 | +export function bumpPackageFile(packagePath, release = 'patch') { |
| 39 | + const source = readFileSync(packagePath, 'utf8'); |
| 40 | + const result = bumpPackageText(source, release); |
| 41 | + const tempPath = `${packagePath}.${process.pid}.tmp`; |
| 42 | + try { |
| 43 | + writeFileSync(tempPath, result.text, { mode: statSync(packagePath).mode & 0o777 }); |
| 44 | + renameSync(tempPath, packagePath); |
| 45 | + } finally { |
| 46 | + try { unlinkSync(tempPath); } catch { /* rename succeeded or no temp was written */ } |
| 47 | + } |
| 48 | + return result; |
| 49 | +} |
| 50 | + |
| 51 | +function main() { |
| 52 | + const release = process.argv[2] || 'patch'; |
| 53 | + const packagePath = resolve(dirname(fileURLToPath(import.meta.url)), '..', 'package.json'); |
| 54 | + const result = bumpPackageFile(packagePath, release); |
| 55 | + console.log(`${result.current} -> ${result.next}`); |
| 56 | +} |
| 57 | + |
| 58 | +if (process.argv[1] && pathToFileURL(resolve(process.argv[1])).href === import.meta.url) { |
| 59 | + try { main(); } catch (err) { |
| 60 | + console.error(`bd-console version bump failed: ${err.message}`); |
| 61 | + process.exitCode = 1; |
| 62 | + } |
| 63 | +} |
0 commit comments