Signed-off-by: ale <ale@manalejandro.com>
Este commit está contenido en:
ale
2025-08-19 04:16:26 +02:00
padre 5b5393ddb5
commit ba2a9b08da

Ver fichero

@@ -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;