|
|
|
@@ -115,7 +115,7 @@ class PackageManager {
|
|
|
|
const packageJson = await fs.readJson(packageJsonPath);
|
|
|
|
const packageJson = await fs.readJson(packageJsonPath);
|
|
|
|
const dependencies = {
|
|
|
|
const dependencies = {
|
|
|
|
...packageJson.dependencies,
|
|
|
|
...packageJson.dependencies,
|
|
|
|
...(options.includeDev ? packageJson.devDependencies : {})
|
|
|
|
...(options.includeDev || options.saveDev ? packageJson.devDependencies : {})
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (Object.keys(dependencies).length === 0) {
|
|
|
|
if (Object.keys(dependencies).length === 0) {
|
|
|
|
@@ -125,8 +125,8 @@ class PackageManager {
|
|
|
|
|
|
|
|
|
|
|
|
const packages = Object.entries(dependencies).map(([name, version]) => `${name}@${version}`);
|
|
|
|
const packages = Object.entries(dependencies).map(([name, version]) => `${name}@${version}`);
|
|
|
|
|
|
|
|
|
|
|
|
// Call the main installation logic directly, bypassing the package.json check
|
|
|
|
// Call the main installation logic with special flag for package.json installs
|
|
|
|
return await this.installPackages(packages, { ...options, fromPackageJson: true });
|
|
|
|
return await this.installPackages(packages, { ...options, fromPackageJsonMain: true });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async installPackages(packages, options = {}) {
|
|
|
|
async installPackages(packages, options = {}) {
|
|
|
|
@@ -134,6 +134,13 @@ class PackageManager {
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Start timer for installation (for main installations only)
|
|
|
|
|
|
|
|
const isMainInstall = !options.fromPackageJson && !options._depth;
|
|
|
|
|
|
|
|
const isPackageJsonMain = options.fromPackageJsonMain;
|
|
|
|
|
|
|
|
const isRecursiveInstall = options._depth > 0;
|
|
|
|
|
|
|
|
const shouldShowSummary = (isMainInstall || isPackageJsonMain) && !isRecursiveInstall;
|
|
|
|
|
|
|
|
const startTime = shouldShowSummary ? Date.now() : null;
|
|
|
|
|
|
|
|
|
|
|
|
console.log(chalk.blue(`Installing ${packages.length} package(s)...`));
|
|
|
|
console.log(chalk.blue(`Installing ${packages.length} package(s)...`));
|
|
|
|
|
|
|
|
|
|
|
|
const results = [];
|
|
|
|
const results = [];
|
|
|
|
@@ -281,21 +288,32 @@ class PackageManager {
|
|
|
|
// Update lock file
|
|
|
|
// Update lock file
|
|
|
|
await this.lock.update(results.filter(r => !r.error));
|
|
|
|
await this.lock.update(results.filter(r => !r.error));
|
|
|
|
|
|
|
|
|
|
|
|
// Show installation summary only for main installations (not dependency installations)
|
|
|
|
// Show installation summary for main installations (including package.json main installs)
|
|
|
|
if (!options.fromPackageJson) {
|
|
|
|
if (shouldShowSummary) {
|
|
|
|
const successfulInstalls = results.filter(r => !r.error);
|
|
|
|
const successfulInstalls = results.filter(r => !r.error);
|
|
|
|
const failedInstalls = results.filter(r => r.error);
|
|
|
|
const failedInstalls = results.filter(r => r.error);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Calculate elapsed time
|
|
|
|
|
|
|
|
const endTime = Date.now();
|
|
|
|
|
|
|
|
const elapsedTime = startTime ? endTime - startTime : 0;
|
|
|
|
|
|
|
|
const elapsedSeconds = (elapsedTime / 1000).toFixed(2);
|
|
|
|
|
|
|
|
|
|
|
|
console.log('');
|
|
|
|
console.log('');
|
|
|
|
console.log(chalk.green('📦 Installation Summary:'));
|
|
|
|
console.log(chalk.green('📦 Installation Summary:'));
|
|
|
|
console.log('');
|
|
|
|
console.log('');
|
|
|
|
|
|
|
|
|
|
|
|
if (successfulInstalls.length > 0) {
|
|
|
|
if (successfulInstalls.length > 0) {
|
|
|
|
console.log(chalk.green(`✓ Successfully installed ${successfulInstalls.length} package(s):`));
|
|
|
|
if (successfulInstalls.length === 1 && !isPackageJsonMain) {
|
|
|
|
successfulInstalls.forEach(result => {
|
|
|
|
const result = successfulInstalls[0];
|
|
|
|
const sourceLabel = result.source === 'cache' ? '(cached)' : `(${result.source})`;
|
|
|
|
const sourceLabel = result.source === 'cache' ? '(cached)' : `(${result.source})`;
|
|
|
|
console.log(chalk.green(` • ${result.name}@${result.version} ${chalk.gray(sourceLabel)}`));
|
|
|
|
console.log(chalk.green(`✓ Successfully installed ${result.name}@${result.version} ${chalk.gray(sourceLabel)}`));
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
|
|
|
|
console.log(chalk.green(`✓ Successfully installed ${successfulInstalls.length} package(s):`));
|
|
|
|
|
|
|
|
successfulInstalls.forEach(result => {
|
|
|
|
|
|
|
|
const sourceLabel = result.source === 'cache' ? '(cached)' : `(${result.source})`;
|
|
|
|
|
|
|
|
console.log(chalk.green(` • ${result.name}@${result.version} ${chalk.gray(sourceLabel)}`));
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (failedInstalls.length > 0) {
|
|
|
|
if (failedInstalls.length > 0) {
|
|
|
|
@@ -307,6 +325,8 @@ class PackageManager {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log('');
|
|
|
|
console.log('');
|
|
|
|
|
|
|
|
console.log(chalk.gray(`⏱️ Total time: ${elapsedSeconds}s`));
|
|
|
|
|
|
|
|
console.log('');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log(chalk.green(`Installation completed. ${results.filter(r => !r.error).length} packages installed.`));
|
|
|
|
console.log(chalk.green(`Installation completed. ${results.filter(r => !r.error).length} packages installed.`));
|
|
|
|
@@ -329,9 +349,13 @@ class PackageManager {
|
|
|
|
...(options.includeDev ? packageJson.devDependencies : {})
|
|
|
|
...(options.includeDev ? packageJson.devDependencies : {})
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const optionalDependencies = packageJson.optionalDependencies || {};
|
|
|
|
|
|
|
|
|
|
|
|
if (!dependencies || Object.keys(dependencies).length === 0) {
|
|
|
|
if (!dependencies || Object.keys(dependencies).length === 0) {
|
|
|
|
// No dependencies to install
|
|
|
|
// If no regular dependencies, check if there are optional dependencies to install
|
|
|
|
return;
|
|
|
|
if (Object.keys(optionalDependencies).length === 0) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Initialize installed packages tracking if not exists
|
|
|
|
// Initialize installed packages tracking if not exists
|
|
|
|
@@ -348,6 +372,14 @@ class PackageManager {
|
|
|
|
|
|
|
|
|
|
|
|
console.log(chalk.blue(`Installing dependencies for ${packageName}...`));
|
|
|
|
console.log(chalk.blue(`Installing dependencies for ${packageName}...`));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Install dependencies recursively by calling installPackages
|
|
|
|
|
|
|
|
const depOptions = {
|
|
|
|
|
|
|
|
...options,
|
|
|
|
|
|
|
|
fromPackageJson: true, // Prevent updating package.json
|
|
|
|
|
|
|
|
_depth: currentDepth + 1, // Increment depth
|
|
|
|
|
|
|
|
_installedPackages: options._installedPackages // Pass along installed packages set
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Filter out already installed packages to avoid duplicates
|
|
|
|
// Filter out already installed packages to avoid duplicates
|
|
|
|
const dependenciesToInstall = Object.entries(dependencies).filter(([name, version]) => {
|
|
|
|
const dependenciesToInstall = Object.entries(dependencies).filter(([name, version]) => {
|
|
|
|
const packageKey = `${name}@${version}`;
|
|
|
|
const packageKey = `${name}@${version}`;
|
|
|
|
@@ -364,37 +396,74 @@ class PackageManager {
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (dependenciesToInstall.length === 0) {
|
|
|
|
if (dependenciesToInstall.length === 0) {
|
|
|
|
return;
|
|
|
|
// No regular dependencies to install, but continue to check optional dependencies
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// Prepare dependency specs for installation
|
|
|
|
|
|
|
|
const dependencySpecs = dependenciesToInstall.map(([name, version]) => {
|
|
|
|
|
|
|
|
// Mark as installed to prevent duplicates
|
|
|
|
|
|
|
|
options._installedPackages.add(`${name}@${version}`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Handle various version formats
|
|
|
|
|
|
|
|
if (version.startsWith('^') || version.startsWith('~') || version.startsWith('>=') || version.startsWith('<=')) {
|
|
|
|
|
|
|
|
return `${name}@${version}`;
|
|
|
|
|
|
|
|
} else if (version === '*' || version === 'latest') {
|
|
|
|
|
|
|
|
return `${name}@latest`;
|
|
|
|
|
|
|
|
} else if (semver.validRange(version)) {
|
|
|
|
|
|
|
|
return `${name}@${version}`;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// For non-semver versions (git urls, file paths, etc.), use as-is
|
|
|
|
|
|
|
|
return `${name}@${version}`;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Install dependencies
|
|
|
|
|
|
|
|
await this.installPackages(dependencySpecs, depOptions);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Prepare dependency specs for installation
|
|
|
|
// Install optional dependencies (ignore failures)
|
|
|
|
const dependencySpecs = dependenciesToInstall.map(([name, version]) => {
|
|
|
|
if (Object.keys(optionalDependencies).length > 0) {
|
|
|
|
// Mark as installed to prevent duplicates
|
|
|
|
const optionalDependenciesToInstall = Object.entries(optionalDependencies).filter(([name, version]) => {
|
|
|
|
options._installedPackages.add(`${name}@${version}`);
|
|
|
|
const packageKey = `${name}@${version}`;
|
|
|
|
|
|
|
|
if (options._installedPackages && options._installedPackages.has(packageKey)) {
|
|
|
|
|
|
|
|
return false; // Skip already installed package
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Handle various version formats
|
|
|
|
// Check if package already exists in node_modules
|
|
|
|
if (version.startsWith('^') || version.startsWith('~') || version.startsWith('>=') || version.startsWith('<=')) {
|
|
|
|
const targetDir = options.global
|
|
|
|
return `${name}@${version}`;
|
|
|
|
? path.join(this.globalRoot, 'node_modules', name)
|
|
|
|
} else if (version === '*' || version === 'latest') {
|
|
|
|
: path.join(this.projectRoot, 'node_modules', name);
|
|
|
|
return `${name}@latest`;
|
|
|
|
|
|
|
|
} else if (semver.validRange(version)) {
|
|
|
|
const exists = fs.existsSync(targetDir);
|
|
|
|
return `${name}@${version}`;
|
|
|
|
return !exists;
|
|
|
|
} else {
|
|
|
|
});
|
|
|
|
// For non-semver versions (git urls, file paths, etc.), use as-is
|
|
|
|
|
|
|
|
return `${name}@${version}`;
|
|
|
|
if (optionalDependenciesToInstall.length > 0) {
|
|
|
|
|
|
|
|
const optionalSpecs = optionalDependenciesToInstall.map(([name, version]) => {
|
|
|
|
|
|
|
|
// Mark as installed to prevent duplicates
|
|
|
|
|
|
|
|
if (options._installedPackages) {
|
|
|
|
|
|
|
|
options._installedPackages.add(`${name}@${version}`);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Handle various version formats
|
|
|
|
|
|
|
|
if (version.startsWith('^') || version.startsWith('~') || version.startsWith('>=') || version.startsWith('<=')) {
|
|
|
|
|
|
|
|
return `${name}@${version}`;
|
|
|
|
|
|
|
|
} else if (version === '*' || version === 'latest') {
|
|
|
|
|
|
|
|
return `${name}@latest`;
|
|
|
|
|
|
|
|
} else if (semver.validRange(version)) {
|
|
|
|
|
|
|
|
return `${name}@${version}`;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
return `${name}@${version}`;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Install optional dependencies (ignore failures)
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
await this.installPackages(optionalSpecs, depOptions);
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
|
|
console.warn(chalk.yellow(`Some optional dependencies for ${packageName} could not be installed (this is usually safe to ignore)`));
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Install dependencies recursively by calling installPackages
|
|
|
|
|
|
|
|
const depOptions = {
|
|
|
|
|
|
|
|
...options,
|
|
|
|
|
|
|
|
fromPackageJson: true, // Prevent updating package.json
|
|
|
|
|
|
|
|
_depth: currentDepth + 1, // Increment depth
|
|
|
|
|
|
|
|
_installedPackages: options._installedPackages // Pass along installed packages set
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Install dependencies
|
|
|
|
|
|
|
|
await this.installPackages(dependencySpecs, depOptions);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
} catch (error) {
|
|
|
|
console.warn(chalk.yellow(`Failed to install dependencies for ${packageName}: ${error.message}`));
|
|
|
|
console.warn(chalk.yellow(`Failed to install dependencies for ${packageName}: ${error.message}`));
|
|
|
|
@@ -688,12 +757,8 @@ class PackageManager {
|
|
|
|
const { spawn } = require('child_process');
|
|
|
|
const { spawn } = require('child_process');
|
|
|
|
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
// Determine shell based on OS
|
|
|
|
// Use cross-platform approach
|
|
|
|
const isWindows = process.platform === 'win32';
|
|
|
|
const childProcess = spawn(script, [], {
|
|
|
|
const shell = isWindows ? 'cmd' : 'sh';
|
|
|
|
|
|
|
|
const shellFlag = isWindows ? '/c' : '-c';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const childProcess = spawn(shell, [shellFlag, script], {
|
|
|
|
|
|
|
|
cwd: this.projectRoot,
|
|
|
|
cwd: this.projectRoot,
|
|
|
|
stdio: options.silent ? 'pipe' : 'inherit',
|
|
|
|
stdio: options.silent ? 'pipe' : 'inherit',
|
|
|
|
shell: true,
|
|
|
|
shell: true,
|
|
|
|
@@ -704,11 +769,12 @@ class PackageManager {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Initialize output variables for silent mode
|
|
|
|
|
|
|
|
let stdout = '';
|
|
|
|
|
|
|
|
let stderr = '';
|
|
|
|
|
|
|
|
|
|
|
|
// Handle silent mode
|
|
|
|
// Handle silent mode
|
|
|
|
if (options.silent) {
|
|
|
|
if (options.silent) {
|
|
|
|
let stdout = '';
|
|
|
|
|
|
|
|
let stderr = '';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (childProcess.stdout) {
|
|
|
|
if (childProcess.stdout) {
|
|
|
|
childProcess.stdout.on('data', (data) => {
|
|
|
|
childProcess.stdout.on('data', (data) => {
|
|
|
|
stdout += data.toString();
|
|
|
|
stdout += data.toString();
|
|
|
|
@@ -720,34 +786,60 @@ class PackageManager {
|
|
|
|
stderr += data.toString();
|
|
|
|
stderr += data.toString();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
childProcess.on('close', (code) => {
|
|
|
|
|
|
|
|
if (code === 0) {
|
|
|
|
|
|
|
|
if (stdout.trim()) {
|
|
|
|
|
|
|
|
console.log(stdout.trim());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
resolve();
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
if (stderr.trim()) {
|
|
|
|
|
|
|
|
console.error(stderr.trim());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
reject(new Error(`Script "${scriptName}" exited with code ${code}`));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
childProcess.on('close', (code) => {
|
|
|
|
|
|
|
|
if (code === 0) {
|
|
|
|
|
|
|
|
console.log(chalk.green(`✓ Script "${scriptName}" completed successfully`));
|
|
|
|
|
|
|
|
resolve();
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
reject(new Error(`Script "${scriptName}" exited with code ${code}`));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Handle process completion
|
|
|
|
|
|
|
|
let completed = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const handleCompletion = (code, signal) => {
|
|
|
|
|
|
|
|
if (completed) return;
|
|
|
|
|
|
|
|
completed = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (options.silent) {
|
|
|
|
|
|
|
|
if (stdout && stdout.trim()) {
|
|
|
|
|
|
|
|
console.log(stdout.trim());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stderr && stderr.trim()) {
|
|
|
|
|
|
|
|
console.error(stderr.trim());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (code === 0) {
|
|
|
|
|
|
|
|
if (!options.silent) {
|
|
|
|
|
|
|
|
console.log(chalk.green(`✓ Script "${scriptName}" completed successfully`));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
resolve();
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
const errorMsg = signal
|
|
|
|
|
|
|
|
? `Script "${scriptName}" was terminated by signal ${signal}`
|
|
|
|
|
|
|
|
: `Script "${scriptName}" exited with code ${code}`;
|
|
|
|
|
|
|
|
reject(new Error(errorMsg));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
childProcess.on('close', handleCompletion);
|
|
|
|
|
|
|
|
childProcess.on('exit', handleCompletion);
|
|
|
|
|
|
|
|
|
|
|
|
childProcess.on('error', (error) => {
|
|
|
|
childProcess.on('error', (error) => {
|
|
|
|
|
|
|
|
if (completed) return;
|
|
|
|
|
|
|
|
completed = true;
|
|
|
|
reject(new Error(`Failed to run script "${scriptName}": ${error.message}`));
|
|
|
|
reject(new Error(`Failed to run script "${scriptName}": ${error.message}`));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Handle process termination signals
|
|
|
|
|
|
|
|
const cleanup = () => {
|
|
|
|
|
|
|
|
if (!completed && !childProcess.killed) {
|
|
|
|
|
|
|
|
childProcess.kill('SIGTERM');
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
|
|
if (!childProcess.killed) {
|
|
|
|
|
|
|
|
childProcess.kill('SIGKILL');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}, 5000);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
process.on('SIGINT', cleanup);
|
|
|
|
|
|
|
|
process.on('SIGTERM', cleanup);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|