UNPKG

@nxworker/workspace

Version:

Nx plugin providing generators for managing workspace files, including the move-file generator for safely moving files between projects while updating all imports

219 lines (218 loc) 10.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); function _export(target, all) { for(var name in all)Object.defineProperty(target, name, { enumerable: true, get: all[name] }); } _export(exports, { default: function() { return _default; }, moveFileGenerator: function() { return moveFileGenerator; } }); const _devkit = require("@nx/devkit"); const _workspace = require("@nx/workspace"); const _jscodeshiftutils = require("./jscodeshift-utils"); const _treecache = require("./tree-cache"); const _cachedtreeexists = require("./cache/cached-tree-exists"); const _getprojectsourcefiles = require("./cache/get-project-source-files"); const _updateprojectsourcefilescache = require("./cache/update-project-source-files-cache"); const _updatefileexistencecache = require("./cache/update-file-existence-cache"); const _getcacheddependentprojects = require("./cache/get-cached-dependent-projects"); const _splitpatterns = require("./path-utils/split-patterns"); const _isprojectempty = require("./project-analysis/is-project-empty"); const _getdependentprojectnames = require("./project-analysis/get-dependent-project-names"); const _readcompilerpaths = require("./project-analysis/read-compiler-paths"); const _resolveandvalidate = require("./validation/resolve-and-validate"); const _executemove = require("./core-operations/execute-move"); /** * Cache for source files per project to avoid repeated tree traversals. * Key: project root path, Value: array of source file paths */ const projectSourceFilesCache = new Map(); /** * Cache for file existence checks to avoid repeated tree.exists() calls. * Key: file path, Value: boolean indicating if file exists */ const fileExistenceCache = new Map(); /** * Cache for dependent project lookups to avoid repeated graph traversals. * Key: project name, Value: set of dependent project names */ const dependencyGraphCache = new Map(); /** * Wrapper for clearAllCaches that passes cache state */ function clearAllCaches() { // Clear local caches projectSourceFilesCache.clear(); fileExistenceCache.clear(); dependencyGraphCache.clear(); // Clear compiler paths cache from project-analysis module (0, _readcompilerpaths.clearCompilerPathsCache)(); } /** * Wrapper for getProjectSourceFiles that passes cache state */ function getProjectSourceFiles(tree, projectRoot) { return (0, _getprojectsourcefiles.getProjectSourceFiles)(tree, projectRoot, projectSourceFilesCache, fileExistenceCache); } /** * Wrapper for updateProjectSourceFilesCache that passes cache state */ function updateProjectSourceFilesCache(projectRoot, oldPath, newPath) { (0, _updateprojectsourcefilescache.updateProjectSourceFilesCache)(projectRoot, oldPath, newPath, projectSourceFilesCache); } /** * Wrapper for cachedTreeExists that passes cache state */ function cachedTreeExists(tree, filePath) { return (0, _cachedtreeexists.cachedTreeExists)(tree, filePath, fileExistenceCache); } /** * Wrapper for updateFileExistenceCache that passes cache state */ function updateFileExistenceCache(filePath, exists) { (0, _updatefileexistencecache.updateFileExistenceCache)(filePath, exists, fileExistenceCache); } async function moveFileGenerator(tree, options) { // Clear all caches at the start of generator execution clearAllCaches(); // Clear AST cache at the start of each move operation (0, _jscodeshiftutils.clearCache)(); const projects = (0, _devkit.getProjects)(tree); // Lazily create project graph only when needed (cross-project moves with exported files) // This improves performance for same-project moves by ~15-20% // The graph is created on first call to getProjectGraphAsync() and cached for subsequent calls let projectGraph = null; const getProjectGraphAsync = async ()=>{ projectGraph ??= await (0, _devkit.createProjectGraphAsync)(); return projectGraph; }; // Support comma-separated file paths and glob patterns // We need to be careful about commas inside brace expansions like {ts,js} const patterns = (0, _splitpatterns.splitPatterns)(options.file); if (patterns.length === 0) { throw new Error('At least one file path or glob pattern must be provided'); } // Expand glob patterns to actual file paths // Separate glob patterns from direct file paths for batch processing const globPatterns = []; const directPaths = []; const patternMap = new Map(); // normalized -> original for error messages for (const pattern of patterns){ // Normalize pattern to use forward slashes (Windows compatibility) const normalizedPattern = (0, _devkit.normalizePath)(pattern); // Check if pattern contains glob characters const isGlobPattern = /[*?[\]{}]/.test(normalizedPattern); if (isGlobPattern) { globPatterns.push(normalizedPattern); patternMap.set(normalizedPattern, pattern); } else { // Direct file path directPaths.push(normalizedPattern); } } // Batch all glob patterns into a single globAsync call for better performance const filePaths = [ ...directPaths ]; if (globPatterns.length > 0) { const matches = await (0, _devkit.globAsync)(tree, globPatterns); // If no matches at all, we need to check individual patterns for better error messages // Only do this in the error case to maintain performance in the success case if (matches.length === 0 && globPatterns.length > 0) { // Find the first pattern that matches nothing for a helpful error message for (const globPattern of globPatterns){ const individualMatches = await (0, _devkit.globAsync)(tree, [ globPattern ]); if (individualMatches.length === 0) { const originalPattern = patternMap.get(globPattern) || globPattern; throw new Error(`No files found matching glob pattern: "${originalPattern}"`); } } // If we get here, all patterns individually matched something, but combined they didn't // This shouldn't happen, but throw a generic error just in case throw new Error(`No files found matching glob patterns: "${globPatterns.join(', ')}"`); } filePaths.push(...matches); } // Remove duplicates (in case multiple patterns match the same file) const uniqueFilePaths = Array.from(new Set(filePaths)); if (uniqueFilePaths.length === 0) { throw new Error('At least one file path must be provided'); } // Validate and resolve all files upfront const contexts = uniqueFilePaths.map((filePath)=>{ const fileOptions = { ...options, file: filePath }; return (0, _resolveandvalidate.resolveAndValidate)(tree, fileOptions, projects, cachedTreeExists, getProjectSourceFiles); }); // Track unique source projects for removal check const sourceProjectNames = new Set(); contexts.forEach((ctx)=>{ sourceProjectNames.add(ctx.sourceProjectName); }); // Execute all moves without deleting sources yet // Note: These must be executed sequentially, not in parallel, because: // 1. Multiple files might be moved to the same target project // 2. updateProjectSourceFilesCache() modifies shared cache arrays // 3. Concurrent modifications could cause race conditions for(let i = 0; i < contexts.length; i++){ const ctx = contexts[i]; const fileOptions = { ...options, file: uniqueFilePaths[i] }; await (0, _executemove.executeMove)(tree, fileOptions, projects, getProjectGraphAsync, ctx, cachedTreeExists, updateProjectSourceFilesCache, updateFileExistenceCache, getProjectSourceFiles, getCachedDependentProjects, true); } // Delete all source files after all moves are complete for (const ctx of contexts){ tree.delete(ctx.normalizedSource); // Update file existence cache updateFileExistenceCache(ctx.normalizedSource, false); // Invalidate tree read cache _treecache.treeReadCache.invalidateFile(ctx.normalizedSource); } // Check if any source projects should be removed if (options.removeEmptyProject) { for (const projectName of sourceProjectNames){ const project = projects.get(projectName); if (project && (0, _isprojectempty.isProjectEmpty)(tree, project)) { _devkit.logger.verbose(`Project ${projectName} is empty, removing it`); try { await (0, _workspace.removeGenerator)(tree, { projectName, skipFormat: true, forceRemove: false }); } catch (error) { _devkit.logger.error(`Failed to remove empty project ${projectName}: ${error}`); } } } } // Format files once at the end if (!options.skipFormat) { await (0, _devkit.formatFiles)(tree); } // Log cache statistics for performance monitoring const cacheStats = (0, _jscodeshiftutils.getCacheStats)(); const fileExistenceCacheSize = fileExistenceCache.size; const projectCacheSize = projectSourceFilesCache.size; // Note: compilerPaths cache is managed in project-analysis module const treeStats = _treecache.treeReadCache.getStats(); const dependencyGraphCacheSize = dependencyGraphCache.size; _devkit.logger.verbose(`AST Cache stats: ${cacheStats.astCacheSize} cached ASTs, ${cacheStats.contentCacheSize} cached files, ${cacheStats.failedParseCount} parse failures`); _devkit.logger.verbose(`File cache stats: ${projectCacheSize} project caches, ${fileExistenceCacheSize} file existence checks`); _devkit.logger.verbose(`Tree cache stats: ${treeStats.contentCacheSize} file reads cached, ${treeStats.childrenCacheSize} directory listings cached`); _devkit.logger.verbose(`Dependency graph cache: ${dependencyGraphCacheSize} project dependencies cached`); } /** * Wrapper for getCachedDependentProjects that passes cache state */ function getCachedDependentProjects(projectGraph, projectName) { return (0, _getcacheddependentprojects.getCachedDependentProjects)(projectGraph, projectName, _getdependentprojectnames.getDependentProjectNames, dependencyGraphCache); } const _default = moveFileGenerator; //# sourceMappingURL=generator.js.map