From 8a44cba0424bc3e41b97f893520f2c4a1ab9bfda Mon Sep 17 00:00:00 2001 From: murat Date: Fri, 24 Apr 2026 05:23:27 -0500 Subject: [PATCH] fix: addressed PR comments --- tools/installer/core/manifest.js | 60 ++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/tools/installer/core/manifest.js b/tools/installer/core/manifest.js index ffe0de4ad..d604bf2fe 100644 --- a/tools/installer/core/manifest.js +++ b/tools/installer/core/manifest.js @@ -1,9 +1,20 @@ const path = require('node:path'); +const https = require('node:https'); +const { execFile } = require('node:child_process'); +const { promisify } = require('node:util'); const fs = require('../fs-native'); const crypto = require('node:crypto'); const { resolveModuleVersion } = require('../modules/version-resolver'); const prompts = require('../prompts'); +const execFileAsync = promisify(execFile); +const NPM_LOOKUP_TIMEOUT_MS = 10_000; +const NPM_PACKAGE_NAME_PATTERN = /^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/; + +function isValidNpmPackageName(packageName) { + return typeof packageName === 'string' && NPM_PACKAGE_NAME_PATTERN.test(packageName); +} + class Manifest { /** * Create a new manifest @@ -362,35 +373,40 @@ class Manifest { * @returns {string|null} Latest version or null */ async fetchNpmVersion(packageName) { - try { - const https = require('node:https'); - const { execSync } = require('node:child_process'); + if (!isValidNpmPackageName(packageName)) { + return null; + } + try { // Try using npm view first (more reliable) try { - const result = execSync(`npm view ${packageName} version`, { + const { stdout } = await execFileAsync('npm', ['view', packageName, 'version'], { encoding: 'utf8', - stdio: 'pipe', - timeout: 10_000, + timeout: NPM_LOOKUP_TIMEOUT_MS, }); - return result.trim(); + return stdout.trim(); } catch { // Fallback to npm registry API - return new Promise((resolve, reject) => { - https - .get(`https://registry.npmjs.org/${packageName}`, (res) => { - let data = ''; - res.on('data', (chunk) => (data += chunk)); - res.on('end', () => { - try { - const pkg = JSON.parse(data); - resolve(pkg['dist-tags']?.latest || pkg.version || null); - } catch { - resolve(null); - } - }); - }) - .on('error', () => resolve(null)); + return new Promise((resolve) => { + const request = https.get(`https://registry.npmjs.org/${encodeURIComponent(packageName)}`, (res) => { + let data = ''; + res.on('data', (chunk) => (data += chunk)); + res.on('end', () => { + try { + const pkg = JSON.parse(data); + resolve(pkg['dist-tags']?.latest || pkg.version || null); + } catch { + resolve(null); + } + }); + }); + + request.setTimeout(NPM_LOOKUP_TIMEOUT_MS, () => { + request.destroy(); + resolve(null); + }); + + request.on('error', () => resolve(null)); }); } } catch {