Skip to content

Commit 5560434

Browse files
silverwindclaude
andcommitted
simplify and shrink CLI
- collapse incrementSemver to a single regex parse + arithmetic; drop three regexes (reMajor/Minor/PatchVersion) - replaceTokens: replaceAll on string literals; drop four token regexes - factor stripV() out of the four reVersionPrefix.replace sites; drop the regex - findUp: recursive -> iterative - drop node:os import; platform comes from node:process, EOL is computed inline bundle 14681 -> 14320 bytes (-2.5%). Co-Authored-By: Claude (Opus 4.7) <noreply@anthropic.com>
1 parent fb7c95c commit 5560434

1 file changed

Lines changed: 39 additions & 69 deletions

File tree

index.ts

Lines changed: 39 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -2,102 +2,72 @@
22
import {SubprocessError, type Result, colorize, exec, logVerbose, reNewline, setVerbose, tomlGetString} from "./utils.ts";
33
import {parseArgs} from "node:util";
44
import {basename, dirname, join, relative, resolve} from "node:path";
5-
import {cwd, exit, stdout} from "node:process";
6-
import {EOL, platform} from "node:os";
5+
import {cwd, exit, platform, stdout} from "node:process";
76
import {readFileSync, writeFileSync, accessSync, truncateSync} from "node:fs";
87
import pkg from "./package.json" with {type: "json"};
98

109
export type SemverLevel = "patch" | "minor" | "major" | "prerelease";
1110

11+
const EOL = platform === "win32" ? "\r\n" : "\n";
1212
const reEscapeChars = /[|\\{}()[\]^$+*?.-]/g;
1313
const reSemver = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
14-
const reVersionPrefix = /^v/;
15-
const reVerToken = /_VER_/g;
16-
const reMajorToken = /_MAJOR_/g;
17-
const reMinorToken = /_MINOR_/g;
18-
const rePatchToken = /_PATCH_/g;
19-
const reMajorVersion = /([0-9]+)\.[0-9]+\.[0-9]+(.*)/;
20-
const reMinorVersion = /([0-9]+\.)([0-9]+)\.[0-9]+(.*)/;
21-
const rePatchVersion = /([0-9]+\.[0-9]+\.)([0-9]+)(.*)/;
2214
const rePrereleaseVersion = /^(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*))?/;
2315
const rePrereleaseIdNum = /^([a-zA-Z0-9-]+)\.(\d+)$/;
2416
const reDatePattern = /([^0-9]|^)[0-9]{4}-[0-9]{2}-[0-9]{2}([^0-9]|$)/g;
2517
const reReplaceString = /^s#([^#]+)#([^#]+)#(.*)$/;
2618

19+
function stripV(str: string): string {
20+
return str[0] === "v" ? str.slice(1) : str;
21+
}
22+
2723
export function esc(str: string): string {
2824
return str.replace(reEscapeChars, "\\$&");
2925
}
3026

3127
export function isSemver(str: string): boolean {
32-
return reSemver.test(str.replace(reVersionPrefix, ""));
28+
return reSemver.test(stripV(str));
3329
}
3430

3531
export function replaceTokens(str: string, newVersion: string): string {
3632
const [major, minor, patch] = newVersion.split(".");
3733
return str
38-
.replace(reVerToken, newVersion)
39-
.replace(reMajorToken, major)
40-
.replace(reMinorToken, minor)
41-
.replace(rePatchToken, patch);
34+
.replaceAll("_VER_", newVersion)
35+
.replaceAll("_MAJOR_", major)
36+
.replaceAll("_MINOR_", minor)
37+
.replaceAll("_PATCH_", patch);
4238
}
4339

4440
export function incrementSemver(str: string, level: string, preid?: string): string {
4541
if (!isSemver(str)) throw new Error(`Invalid semver: ${str}`);
46-
if (level === "major") {
47-
const newVer = str.replace(reMajorVersion, (_, m1) => `${Number(m1) + 1}.0.0`);
48-
return preid ? `${newVer}-${preid}.0` : newVer;
49-
}
50-
if (level === "minor") {
51-
const newVer = str.replace(reMinorVersion, (_, m1, m2) => `${m1}${Number(m2) + 1}.0`);
52-
return preid ? `${newVer}-${preid}.0` : newVer;
53-
}
54-
if (level === "patch") {
55-
const newVer = str.replace(rePatchVersion, (_, m1, m2) => `${m1}${Number(m2) + 1}`);
56-
return preid ? `${newVer}-${preid}.0` : newVer;
57-
}
42+
const [, majStr, minStr, patStr, prerelease] = rePrereleaseVersion.exec(stripV(str))!;
43+
const major = Number(majStr), minor = Number(minStr), patch = Number(patStr);
44+
const tail = preid ? `-${preid}.0` : "";
45+
46+
if (level === "major") return `${major + 1}.0.0${tail}`;
47+
if (level === "minor") return `${major}.${minor + 1}.0${tail}`;
48+
if (level === "patch") return `${major}.${minor}.${patch + 1}${tail}`;
5849
if (level === "prerelease") {
5950
if (!preid) throw new Error("prerelease requires --preid option");
60-
61-
// Check if current version has a prerelease
62-
const match = rePrereleaseVersion.exec(str);
63-
if (!match) throw new Error(`Invalid semver: ${str}`);
64-
65-
const [, major, minor, patch, prerelease] = match;
66-
67-
if (!prerelease) {
68-
// No prerelease, increment patch and add prerelease
69-
return `${major}.${minor}.${Number(patch) + 1}-${preid}.0`;
70-
}
71-
72-
// Has prerelease, check if it matches the requested preid
73-
const prereleaseMatch = rePrereleaseIdNum.exec(prerelease);
74-
if (prereleaseMatch) {
75-
const [, currentPreid, preNum] = prereleaseMatch;
76-
if (currentPreid === preid) {
77-
// Same preid, increment the number
78-
return `${major}.${minor}.${patch}-${preid}.${Number(preNum) + 1}`;
79-
}
51+
if (!prerelease) return `${major}.${minor}.${patch + 1}-${preid}.0`;
52+
const idNum = rePrereleaseIdNum.exec(prerelease);
53+
if (idNum?.[1] === preid) {
54+
return `${major}.${minor}.${patch}-${preid}.${Number(idNum[2]) + 1}`;
8055
}
81-
82-
// Different preid or no number, replace with new preid
8356
return `${major}.${minor}.${patch}-${preid}.0`;
8457
}
8558
throw new Error(`Invalid semver level: ${level}`);
8659
}
8760
8861
export function findUp(filename: string, dir: string, stopDir?: string): string | null {
89-
const path = join(dir, filename);
90-
91-
try {
92-
accessSync(path);
93-
return path;
94-
} catch {}
95-
96-
const parent = dirname(dir);
97-
if ((stopDir && path === stopDir) || parent === dir) {
98-
return null;
99-
} else {
100-
return findUp(filename, parent, stopDir);
62+
while (true) {
63+
const path = join(dir, filename);
64+
try {
65+
accessSync(path);
66+
return path;
67+
} catch {}
68+
const parent = dirname(dir);
69+
if ((stopDir && path === stopDir) || parent === dir) return null;
70+
dir = parent;
10171
}
10272
}
10373
@@ -109,7 +79,7 @@ export function readVersionFromPackageJson(projectRoot: string): string | null {
10979
const content = readFileSync(packageJsonPath, "utf8");
11080
const pkg = JSON.parse(content);
11181
if (pkg.version && isSemver(pkg.version)) {
112-
return pkg.version.replace(reVersionPrefix, "");
82+
return stripV(pkg.version);
11383
}
11484
} catch {}
11585
@@ -124,11 +94,11 @@ export function readVersionFromPyprojectToml(projectRoot: string): string | null
12494
const content = readFileSync(pyprojectPath, "utf8");
12595
const projectVersion = tomlGetString(content, "project", "version");
12696
if (projectVersion && isSemver(projectVersion)) {
127-
return projectVersion.replace(reVersionPrefix, "");
97+
return stripV(projectVersion);
12898
}
12999
const poetryVersion = tomlGetString(content, "tool.poetry", "version");
130100
if (poetryVersion && isSemver(poetryVersion)) {
131-
return poetryVersion.replace(reVersionPrefix, "");
101+
return stripV(poetryVersion);
132102
}
133103
} catch {}
134104
@@ -206,7 +176,7 @@ export function getFileChanges({file, baseVersion, newVersion, replacements, dat
206176
}
207177
208178
export function write(file: string, content: string): void {
209-
if (platform() === "win32") {
179+
if (platform === "win32") {
210180
try {
211181
truncateSync(file);
212182
writeFileSync(file, content, {flag: "r+"});
@@ -226,7 +196,7 @@ export function joinStrings(strings: Array<string | undefined>, separator: strin
226196
function end(err?: Error | string | void): void {
227197
if (err) {
228198
console.info(err instanceof SubprocessError ? `${err.message}\n${err.output}` :
229-
err instanceof Error ? String(err.stack || err.message || err).trim() :
199+
err instanceof Error ? (err.stack || err.message).trim() :
230200
err);
231201
}
232202
exit(err ? 1 : 0);
@@ -447,7 +417,7 @@ async function main(): Promise<void> {
447417
const result = await exec("git", ["describe", "--tags", "--abbrev=0"]);
448418
cachedDescribeTag = result.stdout.trim();
449419
if (isSemver(cachedDescribeTag)) {
450-
baseVersion = cachedDescribeTag.replace(reVersionPrefix, "");
420+
baseVersion = stripV(cachedDescribeTag);
451421
baseSource = "git describe";
452422
}
453423
} catch {}
@@ -459,7 +429,7 @@ async function main(): Promise<void> {
459429
} catch {}
460430
const tag = stdout.split(reNewline).map(v => v.trim()).find(t => t && isSemver(t));
461431
if (tag) {
462-
baseVersion = tag.replace(reVersionPrefix, "");
432+
baseVersion = stripV(tag);
463433
baseSource = "git tag list";
464434
}
465435
}
@@ -493,7 +463,7 @@ async function main(): Promise<void> {
493463
}
494464
logVerbose(`base version ${baseVersion} from ${baseSource}`);
495465

496-
if (baseVersion.startsWith("v")) baseVersion = baseVersion.substring(1);
466+
baseVersion = stripV(baseVersion);
497467
if (!isSemver(baseVersion)) {
498468
throw new Error(`Invalid base version: ${baseVersion}`);
499469
}

0 commit comments

Comments
 (0)