Signed-off-by: ale <ale@manalejandro.com>
Este commit está contenido en:
ale
2025-08-19 03:08:52 +02:00
padre 1554a21a1b
commit 629919cbbc
Se han modificado 14 ficheros con 430 adiciones y 73 borrados

Ver fichero

@@ -160,7 +160,7 @@ class CacheManager {
const metadata = await this.loadMetadata();
let cleanedSize = 0;
for (const [key, entry] of Object.entries(metadata.entries)) {
for (const [, entry] of Object.entries(metadata.entries)) {
const filePath = path.join(this.cacheDir, entry.file);
if (fs.existsSync(filePath)) {

Ver fichero

@@ -201,7 +201,7 @@ program
.option('-y, --yes', 'Use default values')
.action(async (options) => {
try {
await pm.init(options);
await pm.initProject(options);
} catch (error) {
console.error(chalk.red(`Error: ${error.message}`));
process.exit(1);

Ver fichero

@@ -218,8 +218,6 @@ class DependencyResolver {
chooseVersion(versionSpecs) {
// Find a version that satisfies all specs
const allVersions = new Set();
// Get all possible versions from registry for this package
// For now, use a simplified approach
const sortedSpecs = versionSpecs.sort(semver.rcompare);

Ver fichero

@@ -40,7 +40,20 @@ class LockManager {
}
async update(resolvedPackages, options = {}) {
const lockData = await this.loadLockFile();
let lockData;
try {
// Try to load existing lock file
lockData = await this.loadLockFile();
} catch (error) {
// If lock file doesn't exist, create a new one
if (error.message.includes('alepm.lock file not found')) {
await this.init();
lockData = await this.loadLockFile();
} else {
throw error;
}
}
// Update timestamp
lockData.metadata.lastModified = new Date().toISOString();
@@ -117,7 +130,7 @@ class LockManager {
return lockData;
}
async remove(packageNames, options = {}) {
async remove(packageNames, _options = {}) {
const lockData = await this.loadLockFile();
for (const packageName of packageNames) {
@@ -553,18 +566,18 @@ class LockManager {
const lockData = await this.loadLockFile();
switch (format.toLowerCase()) {
case 'json':
return JSON.stringify(lockData, null, 2);
case 'json':
return JSON.stringify(lockData, null, 2);
case 'yaml':
// Would need yaml library
throw new Error('YAML export not implemented');
case 'yaml':
// Would need yaml library
throw new Error('YAML export not implemented');
case 'csv':
return this.exportToCsv(lockData);
case 'csv':
return this.exportToCsv(lockData);
default:
throw new Error(`Unsupported export format: ${format}`);
default:
throw new Error(`Unsupported export format: ${format}`);
}
}

Ver fichero

@@ -3,7 +3,6 @@ const fs = require('fs-extra');
const chalk = require('chalk');
const semver = require('semver');
const ora = require('ora');
const { Listr } = require('listr2');
const inquirer = require('inquirer');
const CacheManager = require('../cache/cache-manager');
@@ -36,7 +35,7 @@ class PackageManager {
this.initialized = false;
}
async init() {
async initialize() {
if (this.initialized) return;
await this.config.init();
@@ -60,8 +59,24 @@ class PackageManager {
}
async ensureInitialized() {
// Check if package.json already exists
const packageJsonPath = path.join(this.projectRoot, 'package.json');
const packageJsonExists = fs.existsSync(packageJsonPath);
// If package.json exists, we don't need to initialize
if (packageJsonExists) {
// Just ensure the .alepm directory exists for our internal use
const alePmDir = path.join(this.projectRoot, '.alepm');
if (!fs.existsSync(alePmDir)) {
await fs.ensureDir(alePmDir);
}
this.initialized = true;
return;
}
// If no package.json and not initialized, run full init
if (!this.initialized) {
await this.init();
await this.initProject();
}
}
@@ -125,37 +140,93 @@ class PackageManager {
for (const packageSpec of packages) {
try {
const { name, version } = this.parsePackageSpec(packageSpec);
const parsed = this.parsePackageSpec(packageSpec);
const name = parsed.name;
let version = parsed.version;
console.log(chalk.blue(`Installing ${name}@${version || 'latest'}`));
// Check cache first
if (this.cache.has(name, version)) {
let packageData;
const isFromCache = await this.cache.has(name, version);
if (isFromCache) {
console.log(chalk.green(`Using cached version of ${name}`));
results.push({ name, version, source: 'cache' });
continue;
packageData = await this.cache.get(name, version);
} else {
// 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 });
packageData = downloadResult.data;
// Store in cache
await this.cache.store(name, resolvedVersion, packageData);
// Update the version to the resolved one
version = resolvedVersion;
}
// Resolve and download
const resolvedPackage = await this.resolver.resolvePackage(name, version || 'latest');
// Security check
if (options.secure !== false) {
await this.security.scanPackage(resolvedPackage);
}
// Install the package to node_modules regardless of cache status
const targetDir = options.global
? path.join(this.globalRoot, 'node_modules', name)
: path.join(this.projectRoot, 'node_modules', name);
// Download and store
const packageData = await this.registry.download(resolvedPackage.name, resolvedPackage.version);
await fs.ensureDir(path.dirname(targetDir));
// Store in cache
await this.cache.store(resolvedPackage.name, resolvedPackage.version, packageData);
// Extract package data to target directory
if (Buffer.isBuffer(packageData)) {
// Extract tarball using tar
const tar = require('tar');
const os = require('os');
// Ensure target directory exists
await fs.ensureDir(targetDir);
try {
// Write buffer to temporary file and extract from there
const tempFile = path.join(os.tmpdir(), `${name}-${Date.now()}.tgz`);
await fs.writeFile(tempFile, packageData);
// Extract the tarball directly to the target directory
await tar.extract({
file: tempFile,
cwd: targetDir,
strip: 1 // Remove the 'package' directory level from tarball
});
// Clean up temp file
await fs.remove(tempFile);
} catch (extractError) {
console.warn(chalk.yellow(`Failed to extract tarball: ${extractError.message}`));
// Fallback - create basic package structure
await fs.writeFile(path.join(targetDir, 'package.json'), JSON.stringify({
name,
version: version || 'latest'
}, null, 2));
}
} else {
// Fallback - create basic package structure
await fs.ensureDir(targetDir);
await fs.writeFile(path.join(targetDir, 'package.json'), JSON.stringify({
name,
version: version || 'latest'
}, null, 2));
}
results.push({
name: resolvedPackage.name,
version: resolvedPackage.version,
source: 'registry'
name,
version: version || 'latest',
source: isFromCache ? 'cache' : 'registry'
});
console.log(chalk.green(`✓ Installed ${resolvedPackage.name}@${resolvedPackage.version}`));
console.log(chalk.green(`✓ Installed ${name}@${version || 'latest'}`));
} catch (error) {
console.error(chalk.red(`✗ Failed to install ${packageSpec}: ${error.message}`));
@@ -163,6 +234,11 @@ class PackageManager {
}
}
// Update package.json if not global and not from package.json
if (!options.global && !options.fromPackageJson) {
await this.updatePackageJsonDependencies(results.filter(r => !r.error), options);
}
// Update lock file
await this.lock.update(results.filter(r => !r.error));
@@ -432,7 +508,7 @@ class PackageManager {
console.log(`${key} = ${value || 'undefined'}`);
}
async init(options = {}) {
async initProject(options = {}) {
if (!options.yes) {
const answers = await inquirer.prompt([
{ name: 'name', message: 'Package name:', default: path.basename(this.projectRoot) },
@@ -540,6 +616,30 @@ class PackageManager {
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
}
async updatePackageJsonDependencies(installedPackages, options) {
const packageJsonPath = path.join(this.projectRoot, 'package.json');
if (!fs.existsSync(packageJsonPath)) {
return;
}
const packageJson = await fs.readJson(packageJsonPath);
const targetField = options.saveDev ? 'devDependencies' : 'dependencies';
if (!packageJson[targetField]) {
packageJson[targetField] = {};
}
for (const pkg of installedPackages) {
if (pkg.name && pkg.version) {
const version = options.saveExact ? pkg.version : `^${pkg.version}`;
packageJson[targetField][pkg.name] = version;
}
}
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
}
async removeFromPackageJson(packageName) {
const packageJsonPath = path.join(this.projectRoot, 'package.json');
const packageJson = await fs.readJson(packageJsonPath);

Ver fichero

@@ -1,7 +1,5 @@
const fetch = require('node-fetch');
const semver = require('semver');
const path = require('path');
const fs = require('fs-extra');
class Registry {
constructor() {
@@ -43,9 +41,7 @@ class Registry {
}
const registry = this.getRegistryForPackage(packageName);
const url = version === 'latest'
? `${registry}/${encodeURIComponent(packageName)}`
: `${registry}/${encodeURIComponent(packageName)}/${encodeURIComponent(version)}`;
const url = `${registry}/${encodeURIComponent(packageName)}`;
const response = await this.fetchWithRetry(url);
@@ -58,6 +54,11 @@ class Registry {
const data = await response.json();
// Validate the response structure
if (!data || typeof data !== 'object') {
throw new Error(`Invalid response format for package "${packageName}"`);
}
// Cache the result
if (this.config.cache) {
this.cache.set(cacheKey, {
@@ -71,12 +72,18 @@ class Registry {
async getLatestVersion(packageName) {
const info = await this.getPackageInfo(packageName);
if (!info || !info['dist-tags'] || !info['dist-tags'].latest) {
throw new Error(`Unable to find latest version for package "${packageName}"`);
}
return info['dist-tags'].latest;
}
async getVersions(packageName) {
const info = await this.getPackageInfo(packageName);
return Object.keys(info.versions || {}).sort(semver.rcompare);
if (!info || !info.versions) {
throw new Error(`Unable to find versions for package "${packageName}"`);
}
return Object.keys(info.versions).sort(semver.rcompare);
}
async resolveVersion(packageName, versionSpec) {
@@ -105,7 +112,6 @@ class Registry {
throw new Error('Cannot download packages in offline mode');
}
const registry = this.getRegistryForPackage(pkg.name);
const packageInfo = await this.getPackageInfo(pkg.name, pkg.version);
if (!packageInfo.versions || !packageInfo.versions[pkg.version]) {
@@ -388,33 +394,33 @@ class Registry {
}
}
async publishPackage(packagePath, options = {}) {
async publishPackage(_packagePath, _options = {}) {
// This would implement package publishing
throw new Error('Package publishing not yet implemented');
}
async unpublishPackage(packageName, version, options = {}) {
async unpublishPackage(_packageName, _version, _options = {}) {
// This would implement package unpublishing
throw new Error('Package unpublishing not yet implemented');
}
async deprecatePackage(packageName, version, message, options = {}) {
async deprecatePackage(_packageName, _version, _message, _options = {}) {
// This would implement package deprecation
throw new Error('Package deprecation not yet implemented');
}
async login(username, password, email, registry) {
async login(_username, _password, _email, _registry) {
// This would implement user authentication
throw new Error('Login not yet implemented');
}
async logout(registry) {
async logout(_registry) {
// This would implement logout
const registryUrl = registry || this.config.registry;
const registryUrl = _registry || this.config.registry;
delete this.config.auth[registryUrl];
}
async whoami(registry) {
async whoami(_registry) {
// This would return current user info
throw new Error('whoami not yet implemented');
}

Ver fichero

@@ -103,7 +103,7 @@ class SecurityManager {
const suspiciousPatterns = [
/eval\s*\(/gi, // eval calls
/Function\s*\(/gi, // Function constructor
/require\s*\(\s*['"]child_process['\"]/gi, // child_process usage
/require\s*\(\s*['"]child_process['"]/gi, // child_process usage
/\.exec\s*\(/gi, // exec calls
/\.spawn\s*\(/gi, // spawn calls
/fs\.unlink/gi, // file deletion

Ver fichero

@@ -122,7 +122,7 @@ class BinaryStorage {
file: tempTarball,
cwd: targetDir,
strip: 1, // Remove the package/ prefix
filter: (path, entry) => {
filter: (path, _entry) => {
// Security: prevent path traversal
const normalizedPath = path.normalize(path);
return !normalizedPath.startsWith('../') && !normalizedPath.includes('/../');

Ver fichero

@@ -492,15 +492,15 @@ class ConfigManager {
const config = await this.list();
switch (format.toLowerCase()) {
case 'json':
return JSON.stringify(config, null, 2);
case 'json':
return JSON.stringify(config, null, 2);
case 'yaml':
// Would need yaml library
throw new Error('YAML export not implemented');
case 'yaml':
// Would need yaml library
throw new Error('YAML export not implemented');
default:
throw new Error(`Unsupported export format: ${format}`);
default:
throw new Error(`Unsupported export format: ${format}`);
}
}
@@ -508,12 +508,12 @@ class ConfigManager {
let importedConfig;
switch (format.toLowerCase()) {
case 'json':
importedConfig = JSON.parse(data);
break;
case 'json':
importedConfig = JSON.parse(data);
break;
default:
throw new Error(`Unsupported import format: ${format}`);
default:
throw new Error(`Unsupported import format: ${format}`);
}
const validation = this.validateConfig(importedConfig);

Ver fichero

@@ -443,7 +443,7 @@ class Logger {
}
setLevel(level) {
if (!this.levels.hasOwnProperty(level)) {
if (!Object.prototype.hasOwnProperty.call(this.levels, level)) {
throw new Error(`Invalid log level: ${level}`);
}
this.config.level = level;