mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +00:00
feat(platform): prefer stable Node.js versions over pre-releases
- Add PRE_RELEASE_PATTERN to identify beta, rc, alpha, nightly, canary, dev, pre versions - Modify findNodeFromVersionManager to try stable versions first - Pre-release versions are used as fallback if no stable version found - Add tests for pre-release detection and version prioritization 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,13 +10,12 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
|
||||
/**
|
||||
* Pattern to match version directories (e.g., "v18.17.0", "18.17.0", "v18")
|
||||
* Intentionally permissive to match pre-release versions (v18.17.0-beta, v18.17.0-rc1)
|
||||
* since localeCompare with numeric:true handles sorting correctly
|
||||
*/
|
||||
/** Pattern to match version directories (e.g., "v18.17.0", "18.17.0", "v18") */
|
||||
const VERSION_DIR_PATTERN = /^v?\d+/;
|
||||
|
||||
/** Pattern to identify pre-release versions (beta, rc, alpha, nightly, canary) */
|
||||
const PRE_RELEASE_PATTERN = /-(beta|rc|alpha|nightly|canary|dev|pre)/i;
|
||||
|
||||
/** Result of finding Node.js executable */
|
||||
export interface NodeFinderResult {
|
||||
/** Path to the Node.js executable */
|
||||
@@ -65,11 +64,8 @@ function isExecutable(filePath: string): boolean {
|
||||
|
||||
/**
|
||||
* Find Node.js executable from version manager directories (NVM, fnm)
|
||||
* Uses semantic version sorting to prefer the latest version
|
||||
*
|
||||
* Note: Version sorting uses localeCompare with numeric:true which handles most cases
|
||||
* correctly (e.g., v18.17.0 > v18.9.0) but may not perfectly sort pre-release versions
|
||||
* (e.g., v20.0.0-beta vs v19.9.9). This is acceptable as we prefer the latest stable.
|
||||
* Uses semantic version sorting to prefer the latest stable version
|
||||
* Pre-release versions (beta, rc, alpha) are deprioritized but used as fallback
|
||||
*/
|
||||
function findNodeFromVersionManager(
|
||||
basePath: string,
|
||||
@@ -78,13 +74,18 @@ function findNodeFromVersionManager(
|
||||
if (!fs.existsSync(basePath)) return null;
|
||||
|
||||
try {
|
||||
const versions = fs
|
||||
const allVersions = fs
|
||||
.readdirSync(basePath)
|
||||
.filter((v) => VERSION_DIR_PATTERN.test(v))
|
||||
// Semantic version sort - newest first using localeCompare with numeric option
|
||||
.sort((a, b) => b.localeCompare(a, undefined, { numeric: true, sensitivity: 'base' }));
|
||||
|
||||
for (const version of versions) {
|
||||
// Separate stable and pre-release versions, preferring stable
|
||||
const stableVersions = allVersions.filter((v) => !PRE_RELEASE_PATTERN.test(v));
|
||||
const preReleaseVersions = allVersions.filter((v) => PRE_RELEASE_PATTERN.test(v));
|
||||
|
||||
// Try stable versions first, then fall back to pre-release
|
||||
for (const version of [...stableVersions, ...preReleaseVersions]) {
|
||||
const nodePath = path.join(basePath, version, binSubpath);
|
||||
if (isExecutable(nodePath)) {
|
||||
return nodePath;
|
||||
|
||||
@@ -4,6 +4,59 @@ import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
describe('node-finder', () => {
|
||||
describe('version sorting and pre-release filtering', () => {
|
||||
// Test the PRE_RELEASE_PATTERN logic indirectly
|
||||
const PRE_RELEASE_PATTERN = /-(beta|rc|alpha|nightly|canary|dev|pre)/i;
|
||||
|
||||
it('should identify pre-release versions correctly', () => {
|
||||
const preReleaseVersions = [
|
||||
'v20.0.0-beta',
|
||||
'v18.17.0-rc1',
|
||||
'v19.0.0-alpha',
|
||||
'v21.0.0-nightly',
|
||||
'v20.0.0-canary',
|
||||
'v18.0.0-dev',
|
||||
'v17.0.0-pre',
|
||||
];
|
||||
|
||||
for (const version of preReleaseVersions) {
|
||||
expect(PRE_RELEASE_PATTERN.test(version)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('should not match stable versions as pre-release', () => {
|
||||
const stableVersions = ['v18.17.0', 'v20.10.0', 'v16.20.2', '18.17.0', 'v21.0.0'];
|
||||
|
||||
for (const version of stableVersions) {
|
||||
expect(PRE_RELEASE_PATTERN.test(version)).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
it('should sort versions with numeric comparison', () => {
|
||||
const versions = ['v18.9.0', 'v18.17.0', 'v20.0.0', 'v8.0.0'];
|
||||
const sorted = [...versions].sort((a, b) =>
|
||||
b.localeCompare(a, undefined, { numeric: true, sensitivity: 'base' })
|
||||
);
|
||||
|
||||
expect(sorted).toEqual(['v20.0.0', 'v18.17.0', 'v18.9.0', 'v8.0.0']);
|
||||
});
|
||||
|
||||
it('should prefer stable over pre-release when filtering', () => {
|
||||
const allVersions = ['v20.0.0-beta', 'v19.9.9', 'v18.17.0', 'v21.0.0-rc1'];
|
||||
|
||||
const stableVersions = allVersions.filter((v) => !PRE_RELEASE_PATTERN.test(v));
|
||||
const preReleaseVersions = allVersions.filter((v) => PRE_RELEASE_PATTERN.test(v));
|
||||
const prioritized = [...stableVersions, ...preReleaseVersions];
|
||||
|
||||
// Stable versions should come first
|
||||
expect(prioritized[0]).toBe('v19.9.9');
|
||||
expect(prioritized[1]).toBe('v18.17.0');
|
||||
// Pre-release versions should come after
|
||||
expect(prioritized[2]).toBe('v20.0.0-beta');
|
||||
expect(prioritized[3]).toBe('v21.0.0-rc1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findNodeExecutable', () => {
|
||||
it("should return 'node' with fallback source when skipSearch is true", () => {
|
||||
const result = findNodeExecutable({ skipSearch: true });
|
||||
|
||||
Reference in New Issue
Block a user