UNPKG

move-prop-types

Version:

"Help quickly replace the prop type checker in older react projects to map to prop-types"

209 lines 8.47 kB
/** * Module dependencies */ import chalk from 'chalk'; import { stdout } from 'process'; import { lstatSync, readdir, readFile, writeFile } from 'fs'; import { exec } from 'child_process'; import { promisify } from 'util'; import { es6PropTypeJust, es6PropTypeLeft, es6PropTypeMiddle, es6PropTypeRight, fileEncoding, importState, reactProto, } from './constants.js'; const execAsync = promisify(exec); const readFileAsync = promisify(readFile); const writeFileAsync = promisify(writeFile); const readdirAsync = promisify(readdir); /** * Install prop-types package */ export const installPackage = async () => { console.log(''); try { // Check if the package is installed in the project await import('prop-types'); console.log(`${chalk.cyan.underline.bold('prop-types')} is already installed in your project`); } catch { console.log('Installing prop-types to your project'); try { const { stdout: installOutput, stderr } = await execAsync('pnpm add prop-types'); if (stderr) { console.log(`stderr: ${stderr}`); console.log(''); return; } // the *entire* stdout (buffered) console.log(`${chalk.hex('#FF6347').bold('Installation underway')}`); console.log(installOutput); console.log(`${chalk.cyan.underline.bold('prop-types')} is now installed`); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.error('Error installing prop-types:', errorMessage); } } }; /** * Write file with ES6 prop-types conversion */ const writeFileAsyncEs6 = async (fileAndPath) => { try { const data = await readFileAsync(fileAndPath, fileEncoding); const dataString = data.toString(); const isPropTypeUsed = es6PropTypeJust.test(dataString) || es6PropTypeLeft.test(dataString) || es6PropTypeMiddle.test(dataString) || es6PropTypeRight.test(dataString); const isPropTypeAlreadyPresent = dataString.indexOf(importState) !== -1; if (!isPropTypeUsed || isPropTypeAlreadyPresent) { return; } let newData = dataString.replace(es6PropTypeJust, ''); newData = newData.replace(es6PropTypeLeft, '{'); newData = newData.replace(es6PropTypeMiddle, ','); newData = newData.replace(es6PropTypeRight, ' }'); // Clean up any double spaces in imports newData = newData.replace(/import React, \{\s+([^}]+)\s+\}/g, 'import React, { $1 }'); newData = newData.replace(/,\s+,/g, ','); newData = newData.replace(/,\s+}/g, ' }'); newData = newData.replace(reactProto, 'PropTypes.'); // Find a good place to insert the import - after the first import or at the beginning const importRegex = /(import.*?['"].*?['"];?\n)/; const match = newData.match(importRegex); if (match) { const insertPosition = newData.indexOf(match[0]) + match[0].length; newData = newData.slice(0, insertPosition) + importState + newData.slice(insertPosition); } else { newData = `${importState}\n${newData}`; } if (newData) { await writeFileAsync(fileAndPath, newData, fileEncoding); console.log(`${chalk.magenta.italic(fileAndPath)} just got ${chalk.green('updated')}!`); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.error(`Error processing file ${fileAndPath}:`, errorMessage); } }; /** * Write file with ES5 prop-types conversion (legacy support) */ /* eslint-disable @typescript-eslint/no-unused-vars */ const writeFileAsyncEs5 = async (fileAndPath) => { try { const data = await readFileAsync(fileAndPath, fileEncoding); const dataString = data.toString(); let newData = dataString.replace(/React\.PropTypes[.]?/g, 'PropTypes.'); newData = newData.replace(/const PropTypes = require\('react'\)\.PropTypes;$/g, ''); newData = newData.replace(/{PropTypes} = require\('react'\)\.PropTypes/g, ''); newData = [ newData.slice(0, newData.indexOf("';\n") + 2), importState, newData.slice(newData.indexOf("';\n") + 2), ].join(''); if (newData) { await writeFileAsync(fileAndPath, newData, fileEncoding); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.error(`Error processing file ${fileAndPath}:`, errorMessage); } }; /** * Update a single file */ export const updateFile = async (cmd, fileAndPath) => { if (!fileAndPath) { console.error('No file path provided'); return; } let targetPath = fileAndPath; // Handle file extension validation if (/[.]/.exec(targetPath)) { if (!/\S+\.(jsx?|tsx?)$/.test(targetPath)) { console.log(`Skipping ${targetPath} - not a .js, .jsx, .ts, or .tsx file`); return; } } else { // Try to find the file with .js, .jsx, .ts, or .tsx extension const fs = await import('fs'); const statAsync = promisify(fs.stat); const extensions = ['.js', '.jsx', '.ts', '.tsx']; let found = false; for (const ext of extensions) { try { await statAsync(`${targetPath}${ext}`); targetPath = `${targetPath}${ext}`; found = true; break; } catch (err) { // Continue to next extension } } if (!found) { console.log(`${chalk.magenta.italic(targetPath)} doesn't ${chalk.red.inverse('seem to exist in the given path')} with extensions .js, .jsx, .ts, or .tsx`); return; } } await writeFileAsyncEs6(targetPath); }; /** * Update all files in a folder recursively */ export const updateFolder = async (cmd, folderName) => { console.log(''); try { const files = await readdirAsync(folderName); const folderInFolder = files.filter((source) => lstatSync(`${folderName}/${source}`).isDirectory()); // Process subdirectories recursively for (const folder of folderInFolder) { await updateFolder('updateFolder', `${folderName}/${folder}`); } const filesInFolder = files.filter((source) => !lstatSync(`${folderName}/${source}`).isDirectory()); // Process files in current directory (filter for supported file types) const supportedFiles = filesInFolder.filter(file => /\.(jsx?|tsx?)$/.test(file)); for (const file of supportedFiles) { await updateFile('updateFolder', `${folderName}/${file}`); } console.log(''); console.log(`folder ${chalk.underline.yellowBright(folderName)} and js/jsx/ts/tsx files inside are now ${chalk.greenBright('ready')}!`); stdout.write('\x1b[2J'); stdout.write('\x1b[0f'); console.log(`Your folder and files have been updated. Thank you for using ${chalk.yellowBright('move-prop-types')}`); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.error(`Error processing folder ${folderName}:`, errorMessage); } }; /** * Display help examples */ export const helpExamples = () => { return ` Examples: $ move-prop-types --help for info $ move-prop-types -P ../dir1/dir2/filename.[js|jsx|ts|tsx] - This will run replace only on the given file. $ move-prop-types -F ../dir1/dir2 - This will run the update for all the files inside the given directory $ move-prop-types -I -F ../dir1/dir2 - This will install prop-types to dependencies and run the update for all the files inside the given directory `; }; /** * Find matching value in array */ export const findMatch = (givenValue, setToMatch) => { if (!Array.isArray(givenValue)) { return ''; } let index = 0; givenValue.filter((val) => { if (val === setToMatch[0] || val === setToMatch[1]) { index = givenValue.indexOf(val) + 1; } }); return index && index < givenValue.length ? givenValue[index] || '' : ''; }; //# sourceMappingURL=helper.js.map