Comparar commits

..

11 Commits

Autor SHA1 Mensaje Fecha
ale
511b545ffb 1.1.0 2025-08-19 23:13:15 +02:00
ale
76e9c939e2 v1.1.0
Signed-off-by: ale <ale@manalejandro.com>
2025-08-19 23:11:19 +02:00
ale
6c575750ff 1.0.7 2025-08-19 06:57:59 +02:00
ale
9eb10395ad v1.0.7
Signed-off-by: ale <ale@manalejandro.com>
2025-08-19 06:57:52 +02:00
ale
e46dd6a16a temporizador
Signed-off-by: ale <ale@manalejandro.com>
2025-08-19 06:51:59 +02:00
ale
30d2b35bda v1.0.6
Signed-off-by: ale <ale@manalejandro.com>
2025-08-19 06:35:06 +02:00
ale
2fccf3fd48 1.0.6 2025-08-19 06:28:19 +02:00
ale
fdeb3b2a2c some fixes
Signed-off-by: ale <ale@manalejandro.com>
2025-08-19 06:28:08 +02:00
ale
a92b2496e1 1.0.5 2025-08-19 06:17:00 +02:00
ale
05836dadd2 workspaces support
Signed-off-by: ale <ale@manalejandro.com>
2025-08-19 06:16:50 +02:00
ale
a6dc54c045 1.0.4 2025-08-19 05:10:05 +02:00
Se han modificado 3 ficheros con 388 adiciones y 86 borrados

Ver fichero

@@ -1,6 +1,6 @@
{ {
"name": "alepm", "name": "alepm",
"version": "1.0.3", "version": "1.1.0",
"description": "Advanced and secure Node.js package manager with binary storage, intelligent caching, and comprehensive security features", "description": "Advanced and secure Node.js package manager with binary storage, intelligent caching, and comprehensive security features",
"main": "src/index.js", "main": "src/index.js",
"bin": { "bin": {
@@ -51,21 +51,14 @@
"IMPLEMENTATION.md" "IMPLEMENTATION.md"
], ],
"dependencies": { "dependencies": {
"body-parser": "^2.2.0",
"chalk": "^4.1.2", "chalk": "^4.1.2",
"commander": "^11.1.0", "commander": "^11.1.0",
"crypto": "^1.0.1",
"debug": "^4.4.1",
"express": "^5.1.0",
"fs-extra": "^11.3.1", "fs-extra": "^11.3.1",
"inquirer": "^8.2.6", "inquirer": "^8.2.6",
"listr2": "^6.6.1",
"lodash": "^4.17.21",
"node-fetch": "^2.6.12", "node-fetch": "^2.6.12",
"ora": "^5.4.1", "ora": "^5.4.1",
"semver": "^7.7.2", "semver": "^7.7.2",
"tar": "^6.2.1", "tar": "^6.2.1"
"lodash.debounce": "^4.0.8"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^8.45.0", "eslint": "^8.45.0",

Ver fichero

@@ -11,13 +11,15 @@ class CacheManager {
constructor() { constructor() {
this.cacheDir = path.join(require('os').homedir(), '.alepm', 'cache'); this.cacheDir = path.join(require('os').homedir(), '.alepm', 'cache');
this.metadataFile = path.join(this.cacheDir, 'metadata.json'); this.metadataFile = path.join(this.cacheDir, 'metadata.json');
this.init(); this._initialized = false;
} }
async init() { async init() {
if (this._initialized) return;
await fs.ensureDir(this.cacheDir); await fs.ensureDir(this.cacheDir);
if (!fs.existsSync(this.metadataFile)) { if (!await fs.pathExists(this.metadataFile)) {
await this.saveMetadata({ await this.saveMetadata({
version: '1.0.0', version: '1.0.0',
entries: {}, entries: {},
@@ -25,9 +27,12 @@ class CacheManager {
lastCleanup: Date.now() lastCleanup: Date.now()
}); });
} }
this._initialized = true;
} }
async get(packageName, version) { async get(packageName, version) {
await this.init();
const key = this.generateKey(packageName, version); const key = this.generateKey(packageName, version);
const metadata = await this.loadMetadata(); const metadata = await this.loadMetadata();
@@ -38,7 +43,7 @@ class CacheManager {
const entry = metadata.entries[key]; const entry = metadata.entries[key];
const filePath = path.join(this.cacheDir, entry.file); const filePath = path.join(this.cacheDir, entry.file);
if (!fs.existsSync(filePath)) { if (!await fs.pathExists(filePath)) {
// Remove stale entry // Remove stale entry
delete metadata.entries[key]; delete metadata.entries[key];
await this.saveMetadata(metadata); await this.saveMetadata(metadata);
@@ -67,6 +72,7 @@ class CacheManager {
} }
async has(packageName, version) { async has(packageName, version) {
await this.init();
const key = this.generateKey(packageName, version); const key = this.generateKey(packageName, version);
const metadata = await this.loadMetadata(); const metadata = await this.loadMetadata();
@@ -78,7 +84,7 @@ class CacheManager {
const filePath = path.join(this.cacheDir, entry.file); const filePath = path.join(this.cacheDir, entry.file);
// Check if file exists // Check if file exists
if (!fs.existsSync(filePath)) { if (!await fs.pathExists(filePath)) {
// Remove stale entry // Remove stale entry
delete metadata.entries[key]; delete metadata.entries[key];
await this.saveMetadata(metadata); await this.saveMetadata(metadata);
@@ -89,6 +95,7 @@ class CacheManager {
} }
async store(packageName, version, data) { async store(packageName, version, data) {
await this.init();
const key = this.generateKey(packageName, version); const key = this.generateKey(packageName, version);
const metadata = await this.loadMetadata(); const metadata = await this.loadMetadata();
@@ -117,7 +124,7 @@ class CacheManager {
if (metadata.entries[key]) { if (metadata.entries[key]) {
const oldEntry = metadata.entries[key]; const oldEntry = metadata.entries[key];
const oldFilePath = path.join(this.cacheDir, oldEntry.file); const oldFilePath = path.join(this.cacheDir, oldEntry.file);
if (fs.existsSync(oldFilePath)) { if (await fs.pathExists(oldFilePath)) {
await fs.remove(oldFilePath); await fs.remove(oldFilePath);
metadata.totalSize -= oldEntry.size; metadata.totalSize -= oldEntry.size;
} }
@@ -135,6 +142,7 @@ class CacheManager {
} }
async remove(packageName, version) { async remove(packageName, version) {
await this.init();
const key = this.generateKey(packageName, version); const key = this.generateKey(packageName, version);
const metadata = await this.loadMetadata(); const metadata = await this.loadMetadata();
@@ -145,7 +153,7 @@ class CacheManager {
const entry = metadata.entries[key]; const entry = metadata.entries[key];
const filePath = path.join(this.cacheDir, entry.file); const filePath = path.join(this.cacheDir, entry.file);
if (fs.existsSync(filePath)) { if (await fs.pathExists(filePath)) {
await fs.remove(filePath); await fs.remove(filePath);
} }
@@ -157,13 +165,14 @@ class CacheManager {
} }
async clean() { async clean() {
await this.init();
const metadata = await this.loadMetadata(); const metadata = await this.loadMetadata();
let cleanedSize = 0; let cleanedSize = 0;
for (const [, entry] of Object.entries(metadata.entries)) { for (const [, entry] of Object.entries(metadata.entries)) {
const filePath = path.join(this.cacheDir, entry.file); const filePath = path.join(this.cacheDir, entry.file);
if (fs.existsSync(filePath)) { if (await fs.pathExists(filePath)) {
await fs.remove(filePath); await fs.remove(filePath);
cleanedSize += entry.size; cleanedSize += entry.size;
} }
@@ -182,6 +191,7 @@ class CacheManager {
} }
async verify() { async verify() {
await this.init();
const metadata = await this.loadMetadata(); const metadata = await this.loadMetadata();
const corrupted = []; const corrupted = [];
const missing = []; const missing = [];
@@ -189,7 +199,7 @@ class CacheManager {
for (const [key, entry] of Object.entries(metadata.entries)) { for (const [key, entry] of Object.entries(metadata.entries)) {
const filePath = path.join(this.cacheDir, entry.file); const filePath = path.join(this.cacheDir, entry.file);
if (!fs.existsSync(filePath)) { if (!await fs.pathExists(filePath)) {
missing.push(key); missing.push(key);
continue; continue;
} }
@@ -220,6 +230,7 @@ class CacheManager {
} }
async getStats() { async getStats() {
await this.init();
const metadata = await this.loadMetadata(); const metadata = await this.loadMetadata();
const entries = Object.values(metadata.entries); const entries = Object.values(metadata.entries);

Ver fichero

@@ -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 = [];
@@ -167,6 +174,10 @@ class PackageManager {
downloadResult = await this.installFromFile(parsed); downloadResult = await this.installFromFile(parsed);
break; break;
} }
case 'workspace': {
downloadResult = await this.installFromWorkspace(parsed);
break;
}
case 'registry': case 'registry':
default: { default: {
// Resolve version first // Resolve version first
@@ -212,7 +223,9 @@ class PackageManager {
try { try {
// Write buffer to temporary file and extract from there // Write buffer to temporary file and extract from there
const tempFile = path.join(os.tmpdir(), `${name}-${Date.now()}.tgz`); // Sanitize package name for file system
const sanitizedName = name.replace(/[@/]/g, '-');
const tempFile = path.join(os.tmpdir(), `${sanitizedName}-${Date.now()}.tgz`);
await fs.writeFile(tempFile, packageData); await fs.writeFile(tempFile, packageData);
// Extract the tarball directly to the target directory // Extract the tarball directly to the target directory
@@ -275,6 +288,47 @@ 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 for main installations (including package.json main installs)
if (shouldShowSummary) {
const successfulInstalls = 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(chalk.green('📦 Installation Summary:'));
console.log('');
if (successfulInstalls.length > 0) {
if (successfulInstalls.length === 1 && !isPackageJsonMain) {
const result = successfulInstalls[0];
const sourceLabel = result.source === 'cache' ? '(cached)' : `(${result.source})`;
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) {
console.log('');
console.log(chalk.red(`✗ Failed to install ${failedInstalls.length} package(s):`));
failedInstalls.forEach(result => {
console.log(chalk.red(`${result.packageSpec}: ${result.error}`));
});
}
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.`));
return results; return results;
} }
@@ -295,10 +349,14 @@ 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
if (Object.keys(optionalDependencies).length === 0) {
return; return;
} }
}
// Initialize installed packages tracking if not exists // Initialize installed packages tracking if not exists
if (!options._installedPackages) { if (!options._installedPackages) {
@@ -314,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}`;
@@ -330,9 +396,8 @@ 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 // Prepare dependency specs for installation
const dependencySpecs = dependenciesToInstall.map(([name, version]) => { const dependencySpecs = dependenciesToInstall.map(([name, version]) => {
// Mark as installed to prevent duplicates // Mark as installed to prevent duplicates
@@ -351,16 +416,54 @@ class PackageManager {
} }
}); });
// 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 // Install dependencies
await this.installPackages(dependencySpecs, depOptions); await this.installPackages(dependencySpecs, depOptions);
}
// Install optional dependencies (ignore failures)
if (Object.keys(optionalDependencies).length > 0) {
const optionalDependenciesToInstall = Object.entries(optionalDependencies).filter(([name, version]) => {
const packageKey = `${name}@${version}`;
if (options._installedPackages && options._installedPackages.has(packageKey)) {
return false; // Skip already installed package
}
// Check if package already exists in node_modules
const targetDir = options.global
? path.join(this.globalRoot, 'node_modules', name)
: path.join(this.projectRoot, 'node_modules', name);
const exists = fs.existsSync(targetDir);
return !exists;
});
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)`));
}
}
}
} 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}`));
@@ -654,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,
@@ -670,11 +769,12 @@ class PackageManager {
} }
}); });
// Handle silent mode // Initialize output variables for silent mode
if (options.silent) {
let stdout = ''; let stdout = '';
let stderr = ''; let stderr = '';
// Handle silent mode
if (options.silent) {
if (childProcess.stdout) { if (childProcess.stdout) {
childProcess.stdout.on('data', (data) => { childProcess.stdout.on('data', (data) => {
stdout += data.toString(); stdout += data.toString();
@@ -686,34 +786,60 @@ class PackageManager {
stderr += data.toString(); stderr += data.toString();
}); });
} }
}
childProcess.on('close', (code) => { // Handle process completion
if (code === 0) { let completed = false;
if (stdout.trim()) {
const handleCompletion = (code, signal) => {
if (completed) return;
completed = true;
if (options.silent) {
if (stdout && stdout.trim()) {
console.log(stdout.trim()); console.log(stdout.trim());
} }
resolve(); if (stderr && stderr.trim()) {
} else {
if (stderr.trim()) {
console.error(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}`));
}
});
} }
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);
}); });
} }
@@ -789,8 +915,18 @@ class PackageManager {
return this.parseFileSpec(spec); return this.parseFileSpec(spec);
} }
// Handle workspace dependencies
if (this.isWorkspaceSpec(spec)) {
return this.parseWorkspaceSpec(spec);
}
// Handle standard npm packages // Handle standard npm packages
const match = spec.match(/^(@?[^@]+)(?:@(.+))?$/); // Improved regex to handle scoped packages like @scope/package@version
const match = spec.match(/^(@[^/]+\/[^@]+|[^@]+)(?:@(.+))?$/);
if (!match) {
throw new Error(`Invalid package specification: ${spec}`);
}
return { return {
name: match[1], name: match[1],
version: match[2] || 'latest', version: match[2] || 'latest',
@@ -808,6 +944,16 @@ class PackageManager {
spec.includes('git@'); spec.includes('git@');
} }
isWorkspaceSpec(spec) {
// Check if it's a workspace dependency
const match = spec.match(/^(@?[^@]+)@(.+)$/);
if (match) {
const version = match[2];
return version.startsWith('workspace:');
}
return false;
}
parseGitSpec(gitSpec) { parseGitSpec(gitSpec) {
let url = gitSpec; let url = gitSpec;
let ref = 'main'; // Changed from 'master' to 'main' (modern default) let ref = 'main'; // Changed from 'master' to 'main' (modern default)
@@ -847,6 +993,23 @@ class PackageManager {
}; };
} }
parseWorkspaceSpec(spec) {
const match = spec.match(/^(@?[^@]+)@workspace:(.*)$/);
if (!match) {
throw new Error(`Invalid workspace spec: ${spec}`);
}
const name = match[1];
const workspaceVersion = match[2]; // Could be *, ^1.0.0, ~1.0.0, etc.
return {
name: name,
version: workspaceVersion,
source: 'workspace',
originalSpec: spec
};
}
parseFileSpec(fileSpec) { parseFileSpec(fileSpec) {
const cleanPath = fileSpec.replace('file:', ''); const cleanPath = fileSpec.replace('file:', '');
const name = path.basename(cleanPath); const name = path.basename(cleanPath);
@@ -1165,7 +1328,8 @@ class PackageManager {
// Create tarball from cloned directory // Create tarball from cloned directory
const tar = require('tar'); const tar = require('tar');
const tarballPath = path.join(os.tmpdir(), `${gitSpec.name}-${Date.now()}.tgz`); const sanitizedName = gitSpec.name.replace(/[@/]/g, '-');
const tarballPath = path.join(os.tmpdir(), `${sanitizedName}-${Date.now()}.tgz`);
await tar.create({ await tar.create({
gzip: true, gzip: true,
@@ -1206,7 +1370,8 @@ class PackageManager {
// Handle directory - create tarball // Handle directory - create tarball
const tar = require('tar'); const tar = require('tar');
const os = require('os'); const os = require('os');
const tarballPath = path.join(os.tmpdir(), `${fileSpec.name}-${Date.now()}.tgz`); const sanitizedName = fileSpec.name.replace(/[@/]/g, '-');
const tarballPath = path.join(os.tmpdir(), `${sanitizedName}-${Date.now()}.tgz`);
await tar.create({ await tar.create({
gzip: true, gzip: true,
@@ -1225,6 +1390,139 @@ class PackageManager {
} }
} }
async installFromWorkspace(workspaceSpec) {
try {
const packageName = workspaceSpec.name;
const workspaceVersion = workspaceSpec.version;
// Find the workspace package
const workspacePackage = await this.findWorkspacePackage(packageName, workspaceVersion);
if (!workspacePackage) {
throw new Error(`Workspace package ${packageName} not found`);
}
// Install from the workspace directory
const fileSpec = {
name: packageName,
path: workspacePackage.path,
source: 'file'
};
return await this.installFromFile(fileSpec);
} catch (error) {
throw new Error(`Failed to install workspace package ${workspaceSpec.name}: ${error.message}`);
}
}
async findWorkspacePackage(packageName, workspaceVersion) {
try {
// Read the workspace root package.json to find workspace configuration
const rootPackageJsonPath = path.join(this.projectRoot, 'package.json');
if (!await fs.pathExists(rootPackageJsonPath)) {
throw new Error('No package.json found in project root');
}
const rootPackageJson = await fs.readJson(rootPackageJsonPath);
// Check for workspace configuration
let workspacePatterns = [];
// Handle different workspace configuration formats
if (rootPackageJson.workspaces) {
if (Array.isArray(rootPackageJson.workspaces)) {
workspacePatterns = rootPackageJson.workspaces;
} else if (rootPackageJson.workspaces.packages) {
workspacePatterns = rootPackageJson.workspaces.packages;
}
}
if (workspacePatterns.length === 0) {
throw new Error('No workspace configuration found');
}
// Search for the package in workspace directories
for (const pattern of workspacePatterns) {
const workspaceDirs = await this.expandWorkspacePattern(pattern);
for (const workspaceDir of workspaceDirs) {
const packageJsonPath = path.join(this.projectRoot, workspaceDir, 'package.json');
if (await fs.pathExists(packageJsonPath)) {
const packageJson = await fs.readJson(packageJsonPath);
if (packageJson.name === packageName) {
// Check if version matches workspace requirement
if (this.matchesWorkspaceVersion(packageJson.version, workspaceVersion)) {
return {
name: packageName,
version: packageJson.version,
path: path.join(this.projectRoot, workspaceDir),
packageJson: packageJson
};
}
}
}
}
}
return null;
} catch (error) {
throw new Error(`Failed to find workspace package ${packageName}: ${error.message}`);
}
}
async expandWorkspacePattern(pattern) {
const glob = require('glob');
try {
// Use glob to expand workspace patterns
const matches = await new Promise((resolve, reject) => {
glob(pattern, { cwd: this.projectRoot }, (err, files) => {
if (err) reject(err);
else resolve(files);
});
});
// Filter to only directories that contain package.json
const workspaceDirs = [];
for (const match of matches) {
const packageJsonPath = path.join(this.projectRoot, match, 'package.json');
if (await fs.pathExists(packageJsonPath)) {
workspaceDirs.push(match);
}
}
return workspaceDirs;
} catch (error) {
console.warn(chalk.yellow(`Warning: Failed to expand workspace pattern ${pattern}: ${error.message}`));
return [];
}
}
matchesWorkspaceVersion(packageVersion, workspaceVersion) {
// Handle different workspace version formats
if (workspaceVersion === '*') {
return true; // Any version matches
}
if (workspaceVersion.startsWith('^') || workspaceVersion.startsWith('~')) {
// Use semver to check if version satisfies range
return semver.satisfies(packageVersion, workspaceVersion);
}
if (workspaceVersion === packageVersion) {
return true; // Exact match
}
// Default to true for other cases (workspace should generally match)
return true;
}
async runPostInstallScripts(packageName, packageDir) { async runPostInstallScripts(packageName, packageDir) {
try { try {
const packageJsonPath = path.join(packageDir, 'package.json'); const packageJsonPath = path.join(packageDir, 'package.json');