From 4cc479dc1cbad72d36a2c67cfd577ba1fb131d68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=81opaci=C5=84ski?= Date: Sat, 20 Jul 2024 16:04:26 +0200 Subject: [PATCH] fix: Library initialization script issues (#58) ## Description This PR much improves and fixes problems with the initialization script. It also changes the java package name in android example app sources. --- bin/find.js | 20 +++-- bin/index.js | 15 +++- bin/rename.js | 205 ++++++++++++++++++++------------------------------ bin/utils.js | 75 ++++++++++++++++-- 4 files changed, 178 insertions(+), 137 deletions(-) diff --git a/bin/find.js b/bin/find.js index b3f60cd..f45bd8a 100644 --- a/bin/find.js +++ b/bin/find.js @@ -1,16 +1,18 @@ import fs from 'fs'; import path from 'path'; -export const findFiles = (dir, fileName, excludeDirs = []) => { +export const findFilesByName = (dir, fileName, excludeDirs = []) => { let results = []; const list = fs.readdirSync(dir); - list.forEach((file) => { + list.forEach(file => { const filePath = path.resolve(dir, file); const stat = fs.statSync(filePath); if (stat && stat.isDirectory() && !excludeDirs.includes(file)) { - results = results.concat(findFiles(filePath, fileName, excludeDirs)); + results = results.concat( + findFilesByName(filePath, fileName, excludeDirs) + ); } else if (stat && stat.isFile() && file === fileName) { results.push(filePath); } @@ -23,13 +25,19 @@ export const findFilesByExtension = (dir, extensions, excludeDirs = []) => { let results = []; const list = fs.readdirSync(dir); - list.forEach((file) => { + list.forEach(file => { const filePath = path.resolve(dir, file); const stat = fs.statSync(filePath); if (stat && stat.isDirectory() && !excludeDirs.includes(file)) { - results = results.concat(findFilesByExtension(filePath, extensions, excludeDirs)); - } else if (stat && stat.isFile() && extensions.some(ext => file.endsWith(ext))) { + results = results.concat( + findFilesByExtension(filePath, extensions, excludeDirs) + ); + } else if ( + stat && + stat.isFile() && + extensions.some(ext => file.endsWith(ext)) + ) { results.push(filePath); } }); diff --git a/bin/index.js b/bin/index.js index 7440c48..8a7ef4f 100755 --- a/bin/index.js +++ b/bin/index.js @@ -5,6 +5,11 @@ import init from './init.js'; import logger from './logger.js'; import { hideBin } from 'yargs/helpers'; +function validateProjectName(projectName) { + const kebabCaseRegex = /^[a-z]+(-[a-z]+)*$/; + return kebabCaseRegex.test(projectName); +} + yargs(hideBin(process.argv)) .command( 'init ', @@ -25,7 +30,15 @@ yargs(hideBin(process.argv)) description: 'Directory to initialize the project in' }); }, - argv => init(argv.projectName, argv.verbose, argv.directory) + argv => { + if (!validateProjectName(argv.projectName)) { + logger.error( + 'Invalid project name provided. Project name must be in kebab-case and lowercase.' + ); + process.exit(1); + } + init(argv.projectName, argv.verbose, argv.directory); + } ) .demandCommand(1, 'You need at least one command before moving on') .help() diff --git a/bin/rename.js b/bin/rename.js index e37369b..3599213 100644 --- a/bin/rename.js +++ b/bin/rename.js @@ -2,30 +2,38 @@ import fs from 'fs'; import path from 'path'; import logger from './logger.js'; -import { readJSON, writeJSON } from './utils.js'; -import { findFiles, findFilesByExtension } from './find.js'; +import { + toCamelCase, + replacePlaceholdersInFile, + replacePlaceholdersInDirectory +} from './utils.js'; +import { findFilesByName, findFilesByExtension } from './find.js'; const LIB_CAMEL_CASE_NAME = '__libraryName__'; const LIB_KEBAB_CASE_NAME = '__library-name__'; -const toCamelCase = str => { - return str.replace(/-([a-z])/g, g => g[1].toUpperCase()); -}; - const renameLibraryPackageDirectory = (projectPath, projectName, verbose) => { if (verbose) { logger.info('Setting library directory name...'); } + const dirPath = path.resolve(projectPath, 'packages', LIB_KEBAB_CASE_NAME); const newDirPath = path.resolve(projectPath, 'packages', projectName); fs.renameSync(dirPath, newDirPath); + if (verbose) { logger.info('Library directory name was set'); } }; const renamePackages = (projectPath, projectName, verbose) => { - const packagePaths = findFiles(projectPath, 'package.json', ['node_modules']); + const packagePaths = findFilesByName(projectPath, 'package.json', [ + 'node_modules' + ]); + const replacements = [ + [LIB_KEBAB_CASE_NAME, projectName], + [LIB_CAMEL_CASE_NAME, toCamelCase(projectName)] + ]; if (verbose) { logger.info( @@ -34,59 +42,38 @@ const renamePackages = (projectPath, projectName, verbose) => { } packagePaths.forEach(packagePath => { - const packageJSON = readJSON(packagePath); - - const packageJSONString = JSON.stringify(packageJSON); - const updatedPackageJSONString = packageJSONString - .replace(new RegExp(LIB_KEBAB_CASE_NAME, 'g'), projectName) - .replace(new RegExp(LIB_CAMEL_CASE_NAME, 'g'), toCamelCase(projectName)); - const updatedPackageJSON = JSON.parse(updatedPackageJSONString); - - writeJSON(packagePath, updatedPackageJSON); - - if (verbose) { - logger.info(`Updated ${packagePath}`); - } + replacePlaceholdersInFile(packagePath, replacements, verbose); }); - logger.info('Package names updated successfully.'); + if (verbose) { + logger.info('Package names were updated successfully.'); + } }; -const renameTSconfigAlias = (projectPath, projectName, verbose) => { - const tsconfigPaths = findFiles(projectPath, 'tsconfig.json', [ +const renameTSconfigs = (projectPath, projectName, verbose) => { + const tsconfigPaths = findFilesByName(projectPath, 'tsconfig.json', [ 'node_modules' ]); + const replacements = [ + [LIB_KEBAB_CASE_NAME, projectName], + [LIB_CAMEL_CASE_NAME, toCamelCase(projectName)] + ]; if (verbose) { logger.info( - `Found ${tsconfigPaths.length} tsconfig.json files. Setting library name aliases...` + `Found ${tsconfigPaths.length} tsconfig.json files. Updating placeholders...` ); } - const librarySrcPath = path.resolve( - projectPath, - 'packages', - projectName, - 'src' - ); - tsconfigPaths.forEach(tsconfigPath => { - const tsconfig = readJSON(tsconfigPath); - const tsconfigDir = path.dirname(tsconfigPath); - const relativeLibrarySrcPath = path - .relative(tsconfigDir, librarySrcPath) - .replace(/\\/g, '/'); - - if (tsconfig.compilerOptions && tsconfig.compilerOptions.paths) { - tsconfig.compilerOptions.paths[projectName] = [relativeLibrarySrcPath]; - writeJSON(tsconfigPath, tsconfig); - if (verbose) { - logger.info(`Updated ${tsconfigPath}`); - } - } + replacePlaceholdersInFile(tsconfigPath, replacements, verbose); }); - logger.info('Library name aliases were set in tsconfig.json files.'); + if (verbose) { + logger.info( + 'Placeholders in tsconfig.json files were updated successfully.' + ); + } }; const renamePlaceholdersInExampleApp = (projectPath, projectName, verbose) => { @@ -95,6 +82,10 @@ const renamePlaceholdersInExampleApp = (projectPath, projectName, verbose) => { const files = findFilesByExtension(exampleAppSrcPath, fileExtensions, [ 'node_modules' ]); + const replacements = [ + [LIB_KEBAB_CASE_NAME, projectName], + [LIB_CAMEL_CASE_NAME, toCamelCase(projectName)] + ]; if (verbose) { logger.info( @@ -103,17 +94,12 @@ const renamePlaceholdersInExampleApp = (projectPath, projectName, verbose) => { } files.forEach(filePath => { - if (verbose) { - logger.info(`Processing file: ${filePath}`); - } - const content = fs.readFileSync(filePath, 'utf8'); - const newContent = content - .replace(new RegExp(LIB_KEBAB_CASE_NAME, 'g'), projectName) - .replace(new RegExp(LIB_CAMEL_CASE_NAME, 'g'), toCamelCase(projectName)); - fs.writeFileSync(filePath, newContent); + replacePlaceholdersInFile(filePath, replacements, verbose); }); - logger.info('Placeholder names were replaced in example app.'); + if (verbose) { + logger.info('Placeholder names were replaced in example app files.'); + } }; const renamePlaceholdersInGithubWorkflows = ( @@ -122,7 +108,15 @@ const renamePlaceholdersInGithubWorkflows = ( verbose ) => { const workflowsPath = path.resolve(projectPath, '.github', 'workflows'); - const yamlFiles = findFilesByExtension(workflowsPath, ['.yml', '.yaml']); + const yamlFiles = findFilesByExtension( + workflowsPath, + ['.yml', '.yaml'], + ['node_modules'] + ); + const replacements = [ + [LIB_KEBAB_CASE_NAME, projectName], + [LIB_CAMEL_CASE_NAME, toCamelCase(projectName)] + ]; if (verbose) { logger.info( @@ -131,90 +125,55 @@ const renamePlaceholdersInGithubWorkflows = ( } yamlFiles.forEach(filePath => { - if (verbose) { - logger.info(`Processing file: ${filePath}`); - } - const content = fs.readFileSync(filePath, 'utf8'); - const newContent = content - .replace(new RegExp(LIB_KEBAB_CASE_NAME, 'g'), projectName) - .replace(new RegExp(LIB_CAMEL_CASE_NAME, 'g'), toCamelCase(projectName)); - fs.writeFileSync(filePath, newContent); + replacePlaceholdersInFile(filePath, replacements, verbose); }); - logger.info( - 'Placeholder names were replaced in .github/workflows YAML files.' - ); + if (verbose) { + logger.info( + 'Placeholder names were replaced in .github/workflows YAML files.' + ); + } }; const renameExpoApp = (projectPath, projectName, verbose) => { const appJsonPath = path.resolve(projectPath, 'example', 'expo', 'app.json'); - if (fs.existsSync(appJsonPath)) { - if (verbose) { - logger.info(`Updating app.json in Expo project at ${appJsonPath}`); - } - const appJson = readJSON(appJsonPath); - appJson.name = toCamelCase(projectName); - appJson.slug = projectName; - writeJSON(appJsonPath, appJson); - if (verbose) { - logger.info('Updated app.json in Expo project'); - } - } else { - if (verbose) { - logger.warn(`app.json not found in Expo project at ${appJsonPath}`); - } + const replacements = [ + [LIB_KEBAB_CASE_NAME, projectName], + [LIB_CAMEL_CASE_NAME, toCamelCase(projectName)] + ]; + + if (verbose) { + logger.info(`Updating app.json in Expo project at ${appJsonPath}`); + } + + replacePlaceholdersInFile(appJsonPath, replacements, verbose); + + if (verbose) { + logger.info('Placeholders in app.json were renamed successfully.'); } }; const renameBareApp = (projectPath, projectName, verbose) => { const iosPath = path.resolve(projectPath, 'example', 'bare', 'ios'); const androidPath = path.resolve(projectPath, 'example', 'bare', 'android'); - const camelCaseName = toCamelCase(projectName); - - const replacePlaceholdersInFiles = dirPath => { - const files = findFilesByExtension(dirPath, [ - '.m', - '.swift', - '.kt', - '.java', - '.xml', - '.gradle' - ]); - files.forEach(filePath => { - const content = fs.readFileSync(filePath, 'utf8'); - const newContent = content - .replace(new RegExp(LIB_CAMEL_CASE_NAME, 'g'), camelCaseName) - .replace(new RegExp(LIB_KEBAB_CASE_NAME, 'g'), projectName); - fs.writeFileSync(filePath, newContent); - if (verbose) { - logger.info(`Updated file: ${filePath}`); - } - }); - }; - - const replacePlaceholdersInFileNames = dirPath => { - const files = findFiles(dirPath, null); - files.forEach(filePath => { - const newFilePath = filePath - .replace(new RegExp(LIB_CAMEL_CASE_NAME, 'g'), camelCaseName) - .replace(new RegExp(LIB_KEBAB_CASE_NAME, 'g'), projectName); - if (newFilePath !== filePath) { - fs.renameSync(filePath, newFilePath); - if (verbose) { - logger.info(`Renamed file: ${filePath} to ${newFilePath}`); - } - } - }); - }; + const appJsonPath = path.resolve(projectPath, 'example', 'bare', 'app.json'); + const replacements = [ + [LIB_KEBAB_CASE_NAME, projectName], + [LIB_CAMEL_CASE_NAME, toCamelCase(projectName)] + ]; + + if (verbose) { + logger.info(`Updating app.json in bare project at ${appJsonPath}`); + } + + replacePlaceholdersInFile(appJsonPath, replacements, verbose); if (verbose) { logger.info('Renaming placeholders in iOS and Android directories'); } - replacePlaceholdersInFiles(iosPath); - replacePlaceholdersInFileNames(iosPath); - replacePlaceholdersInFiles(androidPath); - replacePlaceholdersInFileNames(androidPath); + replacePlaceholdersInDirectory(iosPath, replacements, verbose); + replacePlaceholdersInDirectory(androidPath, replacements, verbose); if (verbose) { logger.info( @@ -227,7 +186,7 @@ export default (projectPath, projectName, verbose) => { logger.info('Setting names in template files...'); renameLibraryPackageDirectory(projectPath, projectName, verbose); renamePackages(projectPath, projectName, verbose); - renameTSconfigAlias(projectPath, projectName, verbose); + renameTSconfigs(projectPath, projectName, verbose); renamePlaceholdersInExampleApp(projectPath, projectName, verbose); renamePlaceholdersInGithubWorkflows(projectPath, projectName, verbose); renameExpoApp(projectPath, projectName, verbose); diff --git a/bin/utils.js b/bin/utils.js index a1b1298..6021127 100644 --- a/bin/utils.js +++ b/bin/utils.js @@ -1,10 +1,71 @@ import fs from 'fs'; +import path from 'path'; -export const readJSON = path => - JSON.parse(fs.readFileSync(new URL(path, import.meta.url))); +import logger from './logger.js'; -export const writeJSON = (path, data) => - fs.writeFileSync( - new URL(path, import.meta.url), - JSON.stringify(data, null, 2) - ); +export const toCamelCase = str => { + return str.replace(/-([a-z])/g, g => g[1].toUpperCase()); +}; + +export const replacePlaceholdersInFile = (filePath, replacements, verbose) => { + const content = fs.readFileSync(filePath, 'utf8'); + let newContent = content; + + replacements.forEach(([placeholder, replacement]) => { + newContent = newContent.replace(new RegExp(placeholder, 'g'), replacement); + }); + + fs.writeFileSync(filePath, newContent); + + if (verbose) { + logger.info(`Updated file: ${filePath}`); + } +}; + +export const replacePlaceholdersInDirectory = ( + dirPath, + replacements, + verbose +) => { + const entries = fs.readdirSync(dirPath, { withFileTypes: true }); + + entries.forEach(entry => { + const entryPath = path.join(dirPath, entry.name); + if (entry.isDirectory()) { + // First, rename the directory if needed + let newDirPath = entryPath; + replacements.forEach(([placeholder, replacement]) => { + newDirPath = newDirPath.replace( + new RegExp(placeholder, 'g'), + replacement + ); + }); + + if (newDirPath !== entryPath) { + fs.renameSync(entryPath, newDirPath); + if (verbose) { + logger.info(`Renamed directory: ${entryPath} to ${newDirPath}`); + } + } + // Recursively process the directory + replacePlaceholdersInDirectory(newDirPath, replacements, verbose); + } else if (entry.isFile()) { + replacePlaceholdersInFile(entryPath, replacements, verbose); + + let newFilePath = entryPath; + replacements.forEach(([placeholder, replacement]) => { + newFilePath = newFilePath.replace( + new RegExp(placeholder, 'g'), + replacement + ); + }); + + if (newFilePath !== entryPath) { + fs.renameSync(entryPath, newFilePath); + if (verbose) { + logger.info(`Renamed file: ${entryPath} to ${newFilePath}`); + } + } + } + }); +};