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
JavaScript
/**
* 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