@@ -143,33 +143,52 @@ class PackageManager {
|
||||
const parsed = this.parsePackageSpec(packageSpec);
|
||||
const name = parsed.name;
|
||||
let version = parsed.version;
|
||||
console.log(chalk.blue(`Installing ${name}@${version || 'latest'}`));
|
||||
const source = parsed.source;
|
||||
|
||||
console.log(chalk.blue(`Installing ${name}@${version || 'latest'} from ${source}`));
|
||||
|
||||
// Check cache first
|
||||
// Check cache first (only for registry packages)
|
||||
let packageData;
|
||||
const isFromCache = await this.cache.has(name, version);
|
||||
const isFromCache = source === 'registry' && await this.cache.has(name, version);
|
||||
|
||||
if (isFromCache) {
|
||||
console.log(chalk.green(`Using cached version of ${name}`));
|
||||
packageData = await this.cache.get(name, version);
|
||||
} else {
|
||||
// Resolve version first
|
||||
const resolvedVersion = await this.registry.resolveVersion(name, version || 'latest');
|
||||
// Handle different sources
|
||||
let downloadResult;
|
||||
|
||||
// Security check
|
||||
if (options.secure !== false) {
|
||||
// Simple security check - skip for now
|
||||
}
|
||||
switch (source) {
|
||||
case 'git':
|
||||
downloadResult = await this.downloadFromGit(parsed);
|
||||
break;
|
||||
case 'file':
|
||||
downloadResult = await this.installFromFile(parsed);
|
||||
break;
|
||||
case 'registry':
|
||||
default:
|
||||
// Resolve version first
|
||||
const resolvedVersion = await this.registry.resolveVersion(name, version || 'latest');
|
||||
|
||||
// Security check
|
||||
if (options.secure !== false) {
|
||||
// Simple security check - skip for now
|
||||
}
|
||||
|
||||
// Download and store
|
||||
const downloadResult = await this.registry.download({ name, version: resolvedVersion });
|
||||
// Download and store
|
||||
downloadResult = await this.registry.download({ name, version: resolvedVersion });
|
||||
|
||||
// Update the version to the resolved one
|
||||
version = resolvedVersion;
|
||||
break;
|
||||
}
|
||||
|
||||
packageData = downloadResult.data;
|
||||
|
||||
// Store in cache
|
||||
await this.cache.store(name, resolvedVersion, packageData);
|
||||
|
||||
// Update the version to the resolved one
|
||||
version = resolvedVersion;
|
||||
// Store in cache (only for registry packages)
|
||||
if (source === 'registry') {
|
||||
await this.cache.store(name, version, packageData);
|
||||
}
|
||||
}
|
||||
|
||||
// Install the package to node_modules regardless of cache status
|
||||
@@ -226,11 +245,14 @@ class PackageManager {
|
||||
source: isFromCache ? 'cache' : 'registry'
|
||||
});
|
||||
|
||||
console.log(chalk.green(`✓ Installed ${name}@${version || 'latest'}`));
|
||||
console.log(chalk.green(`✓ Installed ${name}@${version || 'latest'} from ${source || (isFromCache ? 'cache' : 'registry')}`));
|
||||
|
||||
// Install dependencies recursively (with depth control)
|
||||
await this.installDependenciesRecursively(name, targetDir, options);
|
||||
|
||||
// Run postinstall scripts
|
||||
await this.runPostInstallScripts(name, targetDir);
|
||||
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`✗ Failed to install ${packageSpec}: ${error.message}`));
|
||||
results.push({ packageSpec, error: error.message });
|
||||
@@ -633,10 +655,83 @@ class PackageManager {
|
||||
|
||||
// Helper methods
|
||||
parsePackageSpec(spec) {
|
||||
// Handle git repositories
|
||||
if (this.isGitUrl(spec)) {
|
||||
return this.parseGitSpec(spec);
|
||||
}
|
||||
|
||||
// Handle file paths
|
||||
if (spec.startsWith('file:') || spec.startsWith('./') || spec.startsWith('../') || spec.startsWith('/')) {
|
||||
return this.parseFileSpec(spec);
|
||||
}
|
||||
|
||||
// Handle standard npm packages
|
||||
const match = spec.match(/^(@?[^@]+)(?:@(.+))?$/);
|
||||
return {
|
||||
name: match[1],
|
||||
version: match[2] || 'latest'
|
||||
version: match[2] || 'latest',
|
||||
source: 'registry'
|
||||
};
|
||||
}
|
||||
|
||||
isGitUrl(spec) {
|
||||
return spec.includes('github.com') ||
|
||||
spec.includes('gitlab.com') ||
|
||||
spec.includes('bitbucket.org') ||
|
||||
spec.startsWith('git+') ||
|
||||
spec.startsWith('git://') ||
|
||||
spec.endsWith('.git') ||
|
||||
spec.includes('git@');
|
||||
}
|
||||
|
||||
parseGitSpec(gitSpec) {
|
||||
let url = gitSpec;
|
||||
let ref = 'main'; // Changed from 'master' to 'main' (modern default)
|
||||
let name = null;
|
||||
|
||||
// Extract reference (branch/tag/commit)
|
||||
if (url.includes('#')) {
|
||||
const parts = url.split('#');
|
||||
url = parts[0];
|
||||
ref = parts[1];
|
||||
}
|
||||
|
||||
// Extract package name from URL
|
||||
if (url.includes('github.com/')) {
|
||||
const match = url.match(/github\.com\/([^\/]+)\/([^\/]+?)(\.git)?$/);
|
||||
if (match) {
|
||||
name = match[2].replace('.git', '');
|
||||
}
|
||||
} else if (url.includes('gitlab.com/')) {
|
||||
const match = url.match(/gitlab\.com\/([^\/]+)\/([^\/]+?)(\.git)?$/);
|
||||
if (match) {
|
||||
name = match[2].replace('.git', '');
|
||||
}
|
||||
} else if (url.includes('bitbucket.org/')) {
|
||||
const match = url.match(/bitbucket\.org\/([^\/]+)\/([^\/]+?)(\.git)?$/);
|
||||
if (match) {
|
||||
name = match[2].replace('.git', '');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: name || 'git-package',
|
||||
version: ref,
|
||||
source: 'git',
|
||||
url: url,
|
||||
ref: ref
|
||||
};
|
||||
}
|
||||
|
||||
parseFileSpec(fileSpec) {
|
||||
const cleanPath = fileSpec.replace('file:', '');
|
||||
const name = path.basename(cleanPath);
|
||||
|
||||
return {
|
||||
name: name,
|
||||
version: 'file',
|
||||
source: 'file',
|
||||
path: cleanPath
|
||||
};
|
||||
}
|
||||
|
||||
@@ -793,6 +888,207 @@ class PackageManager {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async downloadFromGit(gitSpec) {
|
||||
const { spawn } = require('child_process');
|
||||
const os = require('os');
|
||||
|
||||
console.log(chalk.blue(`Cloning from Git: ${gitSpec.url}#${gitSpec.ref}`));
|
||||
|
||||
// Create temporary directory for cloning
|
||||
const tempDir = path.join(os.tmpdir(), `alepm-git-${Date.now()}`);
|
||||
|
||||
try {
|
||||
// Try to clone with specified branch first
|
||||
let cloneSuccessful = false;
|
||||
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
const git = spawn('git', ['clone', '--depth', '1', '--branch', gitSpec.ref, gitSpec.url, tempDir], {
|
||||
stdio: 'pipe'
|
||||
});
|
||||
|
||||
git.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
cloneSuccessful = true;
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error(`Git clone failed with code ${code}`));
|
||||
}
|
||||
});
|
||||
|
||||
git.on('error', reject);
|
||||
});
|
||||
} catch (error) {
|
||||
// If clone with branch fails, try without specific branch (will use default)
|
||||
console.log(chalk.yellow(`Branch ${gitSpec.ref} not found, trying default branch...`));
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const git = spawn('git', ['clone', '--depth', '1', gitSpec.url, tempDir], {
|
||||
stdio: 'pipe'
|
||||
});
|
||||
|
||||
git.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
cloneSuccessful = true;
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error(`Git clone failed with code ${code}`));
|
||||
}
|
||||
});
|
||||
|
||||
git.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
if (!cloneSuccessful) {
|
||||
throw new Error('Failed to clone repository');
|
||||
}
|
||||
|
||||
// Read package.json to get package name
|
||||
const packageJsonPath = path.join(tempDir, 'package.json');
|
||||
if (await fs.pathExists(packageJsonPath)) {
|
||||
const packageJson = await fs.readJson(packageJsonPath);
|
||||
gitSpec.name = packageJson.name || gitSpec.name;
|
||||
}
|
||||
|
||||
// Create tarball from cloned directory
|
||||
const tar = require('tar');
|
||||
const tarballPath = path.join(os.tmpdir(), `${gitSpec.name}-${Date.now()}.tgz`);
|
||||
|
||||
await tar.create({
|
||||
gzip: true,
|
||||
file: tarballPath,
|
||||
cwd: tempDir
|
||||
}, ['.']);
|
||||
|
||||
// Read tarball as buffer
|
||||
const tarballData = await fs.readFile(tarballPath);
|
||||
|
||||
// Cleanup
|
||||
await fs.remove(tempDir);
|
||||
await fs.remove(tarballPath);
|
||||
|
||||
return { data: tarballData };
|
||||
|
||||
} catch (error) {
|
||||
// Cleanup on error
|
||||
if (await fs.pathExists(tempDir)) {
|
||||
await fs.remove(tempDir);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async installFromFile(fileSpec) {
|
||||
console.log(chalk.blue(`Installing from file: ${fileSpec.path}`));
|
||||
|
||||
const absolutePath = path.resolve(fileSpec.path);
|
||||
|
||||
if (!await fs.pathExists(absolutePath)) {
|
||||
throw new Error(`File not found: ${absolutePath}`);
|
||||
}
|
||||
|
||||
const stat = await fs.stat(absolutePath);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
// Handle directory - create tarball
|
||||
const tar = require('tar');
|
||||
const os = require('os');
|
||||
const tarballPath = path.join(os.tmpdir(), `${fileSpec.name}-${Date.now()}.tgz`);
|
||||
|
||||
await tar.create({
|
||||
gzip: true,
|
||||
file: tarballPath,
|
||||
cwd: absolutePath
|
||||
}, ['.']);
|
||||
|
||||
const tarballData = await fs.readFile(tarballPath);
|
||||
await fs.remove(tarballPath);
|
||||
|
||||
return { data: tarballData };
|
||||
} else {
|
||||
// Handle file - assume it's a tarball
|
||||
const tarballData = await fs.readFile(absolutePath);
|
||||
return { data: tarballData };
|
||||
}
|
||||
}
|
||||
|
||||
async runPostInstallScripts(packageName, packageDir) {
|
||||
try {
|
||||
const packageJsonPath = path.join(packageDir, 'package.json');
|
||||
|
||||
if (!await fs.pathExists(packageJsonPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const packageJson = await fs.readJson(packageJsonPath);
|
||||
const scripts = packageJson.scripts || {};
|
||||
|
||||
// Run postinstall script if it exists
|
||||
if (scripts.postinstall) {
|
||||
console.log(chalk.blue(`Running postinstall script for ${packageName}...`));
|
||||
|
||||
const { spawn } = require('child_process');
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const npmScript = spawn('npm', ['run', 'postinstall'], {
|
||||
cwd: packageDir,
|
||||
stdio: 'inherit',
|
||||
shell: true
|
||||
});
|
||||
|
||||
npmScript.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
console.log(chalk.green(`✓ Postinstall script completed for ${packageName}`));
|
||||
resolve();
|
||||
} else {
|
||||
console.warn(chalk.yellow(`⚠ Postinstall script failed for ${packageName} (code: ${code})`));
|
||||
resolve(); // Don't fail the installation if postinstall fails
|
||||
}
|
||||
});
|
||||
|
||||
npmScript.on('error', (error) => {
|
||||
console.warn(chalk.yellow(`⚠ Could not run postinstall script for ${packageName}: ${error.message}`));
|
||||
resolve(); // Don't fail the installation if postinstall fails
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Run install script if it exists (legacy)
|
||||
if (scripts.install) {
|
||||
console.log(chalk.blue(`Running install script for ${packageName}...`));
|
||||
|
||||
const { spawn } = require('child_process');
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const npmScript = spawn('npm', ['run', 'install'], {
|
||||
cwd: packageDir,
|
||||
stdio: 'inherit',
|
||||
shell: true
|
||||
});
|
||||
|
||||
npmScript.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
console.log(chalk.green(`✓ Install script completed for ${packageName}`));
|
||||
resolve();
|
||||
} else {
|
||||
console.warn(chalk.yellow(`⚠ Install script failed for ${packageName} (code: ${code})`));
|
||||
resolve(); // Don't fail the installation if install script fails
|
||||
}
|
||||
});
|
||||
|
||||
npmScript.on('error', (error) => {
|
||||
console.warn(chalk.yellow(`⚠ Could not run install script for ${packageName}: ${error.message}`));
|
||||
resolve(); // Don't fail the installation if install script fails
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.warn(chalk.yellow(`⚠ Error running post-install scripts for ${packageName}: ${error.message}`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PackageManager;
|
||||
|
||||
Referencia en una nueva incidencia
Block a user