Skip to content

Instantly share code, notes, and snippets.

@mfp22
Last active May 14, 2024 03:00
Show Gist options
  • Save mfp22/9777d771d6392b83719608d8eb9553fe to your computer and use it in GitHub Desktop.
Save mfp22/9777d771d6392b83719608d8eb9553fe to your computer and use it in GitHub Desktop.
node.js script to create Nx library and move folder into it
const fs = require('fs');
const path = require('path');
const exec = require('child_process').exec;
const ts = require('typescript');
const tsHost = ts.createCompilerHost(
{
allowJs: true,
noEmit: true,
isolatedModules: true,
resolveJsonModule: false,
moduleResolution: ts.ModuleResolutionKind.Classic, // we don't want node_modules
incremental: true,
noLib: true,
noResolve: true,
},
true
);
function delintNode(node) {
if (!ts.isImportDeclaration(node)) {
let froms = [];
ts.forEachChild(node, (processedNode) => {
froms.push(delintNode(processedNode));
});
return froms.flat();
}
const named = node.importClause?.namedBindings?.elements || [];
const name = node.importClause?.name?.escapedText;
const nname = node.importClause?.namedBindings?.name?.escapedText;
const from = node.moduleSpecifier.text;
const typeonly = node.importClause?.isTypeOnly;
return from;
}
function getImports(fileName) {
const sourceFile = tsHost.getSourceFile(
fileName,
ts.ScriptTarget.Latest,
(msg) => {
throw new Error(`Failed to parse ${fileName}: ${msg}`);
}
);
if (!sourceFile) {
throw ReferenceError(`Failed to find file ${fileName}`);
}
return delintNode(sourceFile);
}
// Capture CLI arguments
const args = process.argv.slice(2); // Removes first two elements ("node" and script name)
if (args.length !== 3) {
console.error(
'Usage: node convert-folder-to-library.js "<folder-path>" "<library-name>" "<org-name>"'
);
process.exit(1);
}
const [folderPath, libraryName, orgName] = args;
// Execute the conversion function
convertFolderToLibrary(folderPath, libraryName, orgName).catch(console.error);
// Function to execute shell commands
function executeCommand(command) {
return new Promise((resolve, reject) => {
exec(command, (error, stdout, stderr) => {
if (error) reject(stderr);
else resolve(stdout);
});
});
}
// Compute the relative import path from the file's directory to the target searchPath
function computeRelativeImportPath(fromPath, toPath) {
const fromDir = path.dirname(fromPath);
// Ensure both paths are absolute for accurate calculation
const absoluteFromDir = path.resolve(fromDir);
const absoluteToPath = path.resolve(toPath);
let relativePath = path.relative(absoluteFromDir, absoluteToPath);
if (!relativePath.startsWith('.')) {
relativePath = './' + relativePath;
}
console.log(
'absoluteFromDir',
absoluteFromDir,
'absoluteToPath',
absoluteToPath,
'relativePath',
relativePath
);
return relativePath;
}
// Recursive function to find files that import from a specific path
function findFilesWithImportPath(basePath, searchPath, excludePath = '') {
let filesToUpdate = [];
const filesAndFolders = fs.readdirSync(basePath, { withFileTypes: true });
for (const dirent of filesAndFolders) {
const fullPath = path.join(basePath, dirent.name);
if (!excludePath || fullPath.startsWith(excludePath)) {
console.log(`Skipping excluded directory: ${excludePath}`);
continue;
}
if (dirent.isDirectory()) {
filesToUpdate = filesToUpdate.concat(
findFilesWithImportPath(fullPath, searchPath, excludePath)
);
} else if (dirent.isFile() && path.extname(dirent.name) === '.ts') {
// const fileContent = fs.readFileSync(fullPath, 'utf8');
// console.log('fullPath', fullPath, 'searchPath', searchPath, 'fileContent');
// Calculate the relative import path for this specific file
const relativeImportPath = computeRelativeImportPath(
fullPath,
searchPath
);
// Check if this specific relative import path is used in the file
const importPaths = getImports(fullPath);
console.log('importPaths', importPaths);
console.log('relativeImportPath', relativeImportPath);
if (
importPaths.find((importPath) =>
importPath.startsWith(relativeImportPath)
)
) {
filesToUpdate.push(fullPath);
}
}
}
return filesToUpdate;
}
// Function to update import paths in a file
function updateImportPath(filePath, oldImportPath, newImportPath) {
const fileContent = fs.readFileSync(filePath, 'utf8');
// Construct a pattern to match both direct and relative imports
const oldImportPattern = new RegExp(
`(['"])${oldImportPath}/([^'"]+)(['"])`,
'g'
);
const updatedContent = fileContent.replace(
oldImportPattern,
`$1${newImportPath}/$2$3`
);
// Logging for changes made
if (fileContent !== updatedContent) {
console.log(`Updating import in ${filePath}`);
} else {
console.log(`No change needed for ${filePath}`);
}
fs.writeFileSync(filePath, updatedContent);
}
/**
* Adjust external imports in files within a moved directory based on the new location.
*
* @param {string} oldDir - The original directory path before the move.
* @param {string} newDir - The new directory path after the move.
*/
function adjustExternalImports(oldDir, newDir) {
// Normalize and resolve paths
oldDir = path.resolve(oldDir);
newDir = path.resolve(newDir);
// Calculate the relative path shift needed for the move
const relativePathShift = path.relative(newDir, oldDir);
// Function to recursively update imports in files, accounting for depth.
function updateImportsInFiles(dirPath, depth = 0) {
const files = fs.readdirSync(dirPath);
for (const file of files) {
const fullPath = path.join(dirPath, file);
const stats = fs.statSync(fullPath);
if (stats.isDirectory()) {
updateImportsInFiles(fullPath, depth + 1);
} else if (file.endsWith('.js') || file.endsWith('.ts')) {
let content = fs.readFileSync(fullPath, 'utf8');
const debugPath = 'products/product.service';
// Adjust only external import paths.
content = content.replace(
/(import\s+.*?\s+from\s+['"])(.*?)(['"])/g,
(match, p1, p2, p3) => {
// Count the number of '../' or '..\' navigating out of the current file
const upNavigations =
(p2.match(/\.\.\//g) || []).length +
(p2.match(/\.\.\\/g) || []).length;
if (upNavigations > depth) {
// Calculate the new path with the relative shift
// path.join treats each stop as a folder that would require a '..' to get out of
// So wee need path.direname to get the folder, not the file
const outOfDir = path.relative(path.dirname(fullPath), newDir);
const newDirToOldDir = path.relative(newDir, oldDir);
const oldDirToOldPath = path.relative(
newDir,
path.dirname(fullPath)
);
const oldPathToTarget = p2;
const newImportPath = path.join(
outOfDir,
newDirToOldDir,
oldDirToOldPath,
oldPathToTarget
);
// const targetPath = path.resolve(oldDir, filePathToNewDir, p2); // Full path to the target based on the old directory
// const newImportPath = path.relative(
// path.dirname(fullPath),
// targetPath
// ); // Compute new relative path
const normalizedNewPath = newImportPath.split(path.sep).join('/'); // Ensure forward slashes for import paths
const newImport = `${p1}${normalizedNewPath}${p3}`;
console.log('match', match);
console.log('p1', p1);
console.log('p2', p2);
console.log('p3', p3);
console.log('upNavigations', upNavigations);
console.log('outOfDir', outOfDir);
console.log('newDirToOldDir', newDirToOldDir);
console.log('oldDirToOldPath', oldDirToOldPath);
console.log('oldPathToTarget', oldPathToTarget);
// console.log('filePathToNewDir', filePathToNewDir);
// console.log('targetPath', targetPath);
console.log('newImportPath', newImportPath);
console.log('normalizedNewPath', normalizedNewPath);
console.log('newImport', newImport);
return newImport;
}
// if (upNavigations > depth) {
// // It's external, calculate the new path with the relative shift
// console.log(
// '===============================================================\n\n===========\n\n===========\n\n\n\n'
// );
// const newPath = path.join(relativePathShift, p2);
// const normalizedNewPath = newPath.split(path.sep).join('/'); // Ensure forward slashes for import paths
// const newImport = `${p1}${normalizedNewPath}${p3}`;
// if (content.includes(debugPath)) {
// console.log('match', match);
// console.log('p1', p1);
// console.log('p2', p2);
// console.log('p3', p3);
// console.log('upNavigations', upNavigations);
// console.log('relativePathShift', relativePathShift);
// console.log('newPath', newPath);
// console.log('normalizedNewPath', normalizedNewPath);
// console.log('newImport', newImport);
// }
// return newImport;
// }
return match; // Keep internal imports unchanged
}
);
// Write updated content back to the file.
fs.writeFileSync(fullPath, content, 'utf8');
console.log(`Updated external imports in: ${fullPath}`);
}
}
}
// Initiate the recursive import adjustment.
updateImportsInFiles(newDir);
}
// Main function to convert folder into Nx library
async function convertFolderToLibrary(folderPath, libraryName, orgName) {
// Step 1: Create the Nx library
await executeCommand(
`npx nx generate @nx/angular:library --name=${libraryName} --unitTestRunner=jest --directory=src/libs/${libraryName} --importPath=@${orgName}/${libraryName} --projectNameAndRootFormat=as-provided --no-interactive`
);
// Step 2: Move the folder
const librarySrcPath = `src/libs/${libraryName}/src`;
await executeCommand(`rm -rf ${librarySrcPath}/lib`);
const destinationPath = path.join(librarySrcPath, 'lib');
fs.renameSync(folderPath, destinationPath);
// Step 2.1: Adjust imports in files within the moved directory.
adjustExternalImports(folderPath, destinationPath);
// Step 3: Update import paths in the project
const excludePath = path.join('src', 'libs', libraryName);
console.log('path.resolve(folderPath)', path.resolve(folderPath));
const filesToUpdate = findFilesWithImportPath(
'./src/',
path.resolve(folderPath),
excludePath
);
console.log(`Files to update imports: ${filesToUpdate.join(', ')}`); // Log files identified for updating
for (const file of filesToUpdate) {
console.log(
`Updating import in ${file} from ${path.basename(
folderPath
)} to @${orgName}/${libraryName}`
); // Log each update
const relativeImportPath = computeRelativeImportPath(
file,
path.resolve(folderPath)
);
console.log('relativeImportPath', relativeImportPath);
updateImportPath(file, relativeImportPath, `@${orgName}/${libraryName}`);
}
// Step 4: Update index.ts with exports
const filesInLib = fs.readdirSync(destinationPath);
const indexTsPath = path.join(librarySrcPath, 'index.ts');
let exportsContent = filesInLib
.filter((file) => path.extname(file) === '.ts')
.map((file) => `export * from './lib/${path.basename(file, '.ts')}';`)
.join('\n');
fs.writeFileSync(indexTsPath, exportsContent);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment