workspaces support

Signed-off-by: ale <ale@manalejandro.com>
Este commit está contenido en:
ale
2025-08-19 06:16:50 +02:00
padre a6dc54c045
commit 05836dadd2

Ver fichero

@@ -167,6 +167,10 @@ class PackageManager {
downloadResult = await this.installFromFile(parsed);
break;
}
case 'workspace': {
downloadResult = await this.installFromWorkspace(parsed);
break;
}
case 'registry':
default: {
// Resolve version first
@@ -789,6 +793,11 @@ class PackageManager {
return this.parseFileSpec(spec);
}
// Handle workspace dependencies
if (this.isWorkspaceSpec(spec)) {
return this.parseWorkspaceSpec(spec);
}
// Handle standard npm packages
const match = spec.match(/^(@?[^@]+)(?:@(.+))?$/);
return {
@@ -808,6 +817,16 @@ class PackageManager {
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) {
let url = gitSpec;
let ref = 'main'; // Changed from 'master' to 'main' (modern default)
@@ -847,6 +866,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) {
const cleanPath = fileSpec.replace('file:', '');
const name = path.basename(cleanPath);
@@ -1225,6 +1261,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) {
try {
const packageJsonPath = path.join(packageDir, 'package.json');