Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/bolt-optimize-compareto.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"node-version": patch
---

⚡ Bolt: Optimize version comparison by replacing regex and string allocations with a single-pass manual parser (~6x faster).
87 changes: 71 additions & 16 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,31 +52,86 @@
* Compare the current node version with a target version string.
*/
const compareTo = (target: string): number => {
if (target !== target.trim() || target.length === 0) {
return NaN;
}
// Optimization: Avoid allocations for trim() and regex
const len = target.length;
if (len === 0) return NaN;

const stripped = target.replace(/^v/i, "");
// Check for whitespace at start or end
const firstChar = target.charCodeAt(0);
const lastChar = target.charCodeAt(len - 1);
if (firstChar <= 32 || lastChar <= 32) return NaN;

if (stripped.length === 0) {
return NaN;
// Handle 'v' prefix
let start = 0;
if (firstChar === 118 || firstChar === 86) { // 'v' or 'V'
start = 1;
if (len === 1) return NaN; // Just "v"
}

const s2 = stripped.split(".");
// Iterate manually to parse segments
let segmentVal = 0;
let segmentStart = start;
let partIndex = 0;
let result = 0;
let determined = false;

for (let i = start; i <= len; i++) {
const charCode = i < len ? target.charCodeAt(i) : 46; // Use dot for end of string to trigger flush

if (charCode === 46) { // '.'
if (i === segmentStart) return NaN; // Empty segment (e.g. "..")

if (!determined) {
const n1 = nodeVersionParts[partIndex] || 0;
const n2 = segmentVal;

if (n1 > n2) {
result = 1;
determined = true;
} else if (n1 < n2) {
result = -1;
determined = true;
}
partIndex++;
}

for (const segment of s2) {
if (segment === "" || !/^\d+$/.test(segment)) {
return NaN;
segmentVal = 0;
segmentStart = i + 1;
} else if (charCode >= 48 && charCode <= 57) { // '0'-'9'
segmentVal = segmentVal * 10 + (charCode - 48);
} else {
return NaN; // Invalid character
}
}

const len = Math.max(nodeVersionParts.length, s2.length);
if (determined) {
return result;
}

// If not determined, we processed all parts of target and they matched node parts so far.
// We need to check if nodeVersion has more non-zero parts.
// But wait, the previous loop increments partIndex.

// Example 1: Node="20.10.1", Target="20.10"
// Loop processed 20 (match), 10 (match). partIndex is 2.
// We need to check nodeVersionParts[2] which is 1.

// Example 2: Node="20.10", Target="20.10.1"
// Loop processed 20 (match), 10 (match). partIndex is 2.
// Loop processed 1.
// nodeVersionParts[2] is 0 (undefined->0). Target segment is 1.
// 0 < 1 => result = -1. determined=true.
// So this case is handled inside the loop IF nodeVersionParts returns 0 for out of bounds.
// nodeVersionParts is array of numbers. nodeVersionParts[partIndex] || 0 is used.
// So yes, it handles Target being longer.

for (let i = 0; i < len; i++) {
const n1 = nodeVersionParts[i] || 0;
const n2 = Number(s2[i]) || 0;
if (n1 > n2) return 1;
if (n1 < n2) return -1;
// So we only need to handle Node being longer.
if (partIndex < nodeVersionParts.length) {
for (let k = partIndex; k < nodeVersionParts.length; k++) {
const n1 = nodeVersionParts[k];
if (n1 > 0) return 1;

Check failure on line 132 in src/index.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 22.x)

'n1' is possibly 'undefined'.

Check failure on line 132 in src/index.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 24.x)

'n1' is possibly 'undefined'.
if (n1 < 0) return -1;

Check failure on line 133 in src/index.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 22.x)

'n1' is possibly 'undefined'.

Check failure on line 133 in src/index.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 24.x)

'n1' is possibly 'undefined'.
}
}

return 0;
Expand Down
Loading