UNPKG

nx

Version:

The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.

261 lines (260 loc) 11 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PnpmCatalogManager = void 0; const js_yaml_1 = require("@zkochan/js-yaml"); const node_fs_1 = require("node:fs"); const node_path_1 = require("node:path"); const fileutils_1 = require("../fileutils"); const output_1 = require("../output"); /** * PNPM-specific catalog manager implementation */ class PnpmCatalogManager { constructor() { this.name = 'pnpm'; this.catalogProtocol = 'catalog:'; } isCatalogReference(version) { return version.startsWith(this.catalogProtocol); } parseCatalogReference(version) { if (!this.isCatalogReference(version)) { return null; } const catalogName = version.substring(this.catalogProtocol.length); // Normalize both "catalog:" and "catalog:default" to the same representation const isDefault = !catalogName || catalogName === 'default'; return { catalogName: isDefault ? undefined : catalogName, isDefaultCatalog: isDefault, }; } getCatalogDefinitionFilePaths() { return ['pnpm-workspace.yaml']; } getCatalogDefinitions(treeOrRoot) { if (typeof treeOrRoot === 'string') { const pnpmWorkspacePath = (0, node_path_1.join)(treeOrRoot, 'pnpm-workspace.yaml'); if (!(0, node_fs_1.existsSync)(pnpmWorkspacePath)) { return null; } return readYamlFileFromFs(pnpmWorkspacePath); } else { if (!treeOrRoot.exists('pnpm-workspace.yaml')) { return null; } return readYamlFileFromTree(treeOrRoot, 'pnpm-workspace.yaml'); } } resolveCatalogReference(treeOrRoot, packageName, version) { const catalogRef = this.parseCatalogReference(version); if (!catalogRef) { return null; } const workspaceConfig = this.getCatalogDefinitions(treeOrRoot); if (!workspaceConfig) { return null; } let catalogToUse; if (catalogRef.isDefaultCatalog) { // Check both locations for default catalog catalogToUse = workspaceConfig.catalog ?? workspaceConfig.catalogs?.default; } else if (catalogRef.catalogName) { catalogToUse = workspaceConfig.catalogs?.[catalogRef.catalogName]; } return catalogToUse?.[packageName] || null; } validateCatalogReference(treeOrRoot, packageName, version) { const catalogRef = this.parseCatalogReference(version); if (!catalogRef) { throw new Error(`Invalid catalog reference syntax: "${version}". Expected format: "catalog:" or "catalog:name"`); } const workspaceConfig = this.getCatalogDefinitions(treeOrRoot); if (!workspaceConfig) { throw new Error(formatCatalogError('Cannot get Pnpm catalog definitions. No pnpm-workspace.yaml found in workspace root.', ['Create a pnpm-workspace.yaml file in your workspace root'])); } let catalogToUse; if (catalogRef.isDefaultCatalog) { const hasCatalog = !!workspaceConfig.catalog; const hasCatalogsDefault = !!workspaceConfig.catalogs?.default; // Error if both defined (matches pnpm behavior) if (hasCatalog && hasCatalogsDefault) { throw new Error("The 'default' catalog was defined multiple times. Use the 'catalog' field or 'catalogs.default', but not both."); } catalogToUse = workspaceConfig.catalog ?? workspaceConfig.catalogs?.default; if (!catalogToUse) { const availableCatalogs = Object.keys(workspaceConfig.catalogs || {}); const suggestions = [ 'Define a default catalog in pnpm-workspace.yaml under the "catalog" key', ]; if (availableCatalogs.length > 0) { suggestions.push(`Or select from the available named catalogs: ${availableCatalogs .map((c) => `"catalog:${c}"`) .join(', ')}`); } throw new Error(formatCatalogError('No default catalog defined in pnpm-workspace.yaml', suggestions)); } } else if (catalogRef.catalogName) { catalogToUse = workspaceConfig.catalogs?.[catalogRef.catalogName]; if (!catalogToUse) { const availableCatalogs = Object.keys(workspaceConfig.catalogs || {}).filter((c) => c !== 'default'); const defaultCatalog = !!workspaceConfig.catalog ? 'catalog' : !workspaceConfig.catalogs?.default ? 'catalogs.default' : null; const suggestions = [ 'Define the catalog in pnpm-workspace.yaml under the "catalogs" key', ]; if (availableCatalogs.length > 0) { suggestions.push(`Or select from the available named catalogs: ${availableCatalogs .map((c) => `"catalog:${c}"`) .join(', ')}`); } if (defaultCatalog) { suggestions.push(`Or use the default catalog ("${defaultCatalog}")`); } throw new Error(formatCatalogError(`Catalog "${catalogRef.catalogName}" not found in pnpm-workspace.yaml`, suggestions)); } } if (!catalogToUse[packageName]) { let catalogName; if (catalogRef.isDefaultCatalog) { // Context-aware messaging based on which location exists const hasCatalog = !!workspaceConfig.catalog; catalogName = hasCatalog ? 'default catalog ("catalog")' : 'default catalog ("catalogs.default")'; } else { catalogName = `catalog '${catalogRef.catalogName}'`; } const availablePackages = Object.keys(catalogToUse); const suggestions = [ `Add "${packageName}" to ${catalogName} in pnpm-workspace.yaml`, ]; if (availablePackages.length > 0) { suggestions.push(`Or select from the available packages in ${catalogName}: ${availablePackages .map((p) => `"${p}"`) .join(', ')}`); } throw new Error(formatCatalogError(`Package "${packageName}" not found in ${catalogName}`, suggestions)); } } updateCatalogVersions(treeOrRoot, updates) { let checkExists; let readYaml; let writeYaml; if (typeof treeOrRoot === 'string') { const workspaceYamlPath = (0, node_path_1.join)(treeOrRoot, 'pnpm-workspace.yaml'); checkExists = () => (0, node_fs_1.existsSync)(workspaceYamlPath); readYaml = () => (0, node_fs_1.readFileSync)(workspaceYamlPath, 'utf-8'); writeYaml = (content) => (0, node_fs_1.writeFileSync)(workspaceYamlPath, content, 'utf-8'); } else { checkExists = () => treeOrRoot.exists('pnpm-workspace.yaml'); readYaml = () => treeOrRoot.read('pnpm-workspace.yaml', 'utf-8'); writeYaml = (content) => treeOrRoot.write('pnpm-workspace.yaml', content); } if (!checkExists()) { output_1.output.warn({ title: 'No pnpm-workspace.yaml found', bodyLines: [ 'Cannot update catalog versions without a pnpm-workspace.yaml file.', 'Create a pnpm-workspace.yaml file to use catalogs.', ], }); return; } try { const workspaceContent = readYaml(); const workspaceData = (0, js_yaml_1.load)(workspaceContent) || {}; let hasChanges = false; for (const update of updates) { const { packageName, version, catalogName } = update; const normalizedCatalogName = catalogName === 'default' ? undefined : catalogName; let targetCatalog; if (!normalizedCatalogName) { // Default catalog - update whichever exists, prefer catalog over catalogs.default if (workspaceData.catalog) { targetCatalog = workspaceData.catalog; } else if (workspaceData.catalogs?.default) { targetCatalog = workspaceData.catalogs.default; } else { // Neither exists, create catalog (shorthand syntax) workspaceData.catalog ??= {}; targetCatalog = workspaceData.catalog; } } else { // Named catalog workspaceData.catalogs ??= {}; workspaceData.catalogs[normalizedCatalogName] ??= {}; targetCatalog = workspaceData.catalogs[normalizedCatalogName]; } if (targetCatalog[packageName] !== version) { targetCatalog[packageName] = version; hasChanges = true; } } if (hasChanges) { writeYaml((0, js_yaml_1.dump)(workspaceData, { indent: 2, quotingType: '"', forceQuotes: true, })); } } catch (error) { output_1.output.error({ title: 'Failed to update catalog versions', bodyLines: [error instanceof Error ? error.message : String(error)], }); throw error; } } } exports.PnpmCatalogManager = PnpmCatalogManager; function readYamlFileFromFs(path) { try { return (0, fileutils_1.readYamlFile)(path); } catch (error) { output_1.output.warn({ title: 'Unable to parse pnpm-workspace.yaml', bodyLines: [error.toString()], }); return null; } } function readYamlFileFromTree(tree, path) { const content = tree.read(path, 'utf-8'); const { load } = require('@zkochan/js-yaml'); try { return load(content, { filename: path }); } catch (error) { output_1.output.warn({ title: 'Unable to parse pnpm-workspace.yaml', bodyLines: [error.toString()], }); return null; } } function formatCatalogError(error, suggestions) { let message = error; if (suggestions && suggestions.length > 0) { message += '\n\nSuggestions:'; suggestions.forEach((suggestion) => { message += `\n • ${suggestion}`; }); } return message; }