@mui/codemod
Version:
Codemod scripts for Material UI.
216 lines (192 loc) • 6.03 kB
JavaScript
const childProcess = require('child_process');
const { promises: fs } = require('fs');
const path = require('path');
const yargs = require('yargs');
const jscodeshiftPackage = require('jscodeshift/package.json');
const postcssCliPackage = require('postcss-cli/package.json');
const jscodeshiftDirectory = path.dirname(require.resolve('jscodeshift'));
const jscodeshiftExecutable = path.join(jscodeshiftDirectory, jscodeshiftPackage.bin.jscodeshift);
const postcssCliDirectory = path.dirname(require.resolve('postcss-cli'));
const postcssExecutable = path.join(postcssCliDirectory, postcssCliPackage.bin.postcss);
async function runJscodeshiftTransform(transform, files, flags, codemodFlags) {
const paths = [
path.resolve(__dirname, './src', `${transform}/index.js`),
path.resolve(__dirname, './src', `${transform}.js`),
path.resolve(__dirname, './', `${transform}/index.js`),
path.resolve(__dirname, './', `${transform}.js`),
];
let transformerPath;
let error;
for (const item of paths) {
try {
// eslint-disable-next-line no-await-in-loop
await fs.stat(item);
error = undefined;
transformerPath = item;
break;
} catch (srcPathError) {
error = srcPathError;
continue;
}
}
if (error) {
if (error?.code === 'ENOENT') {
throw new Error(
`Transform '${transform}' not found. Check out ${path.resolve(
__dirname,
'./README.md for a list of available codemods.',
)}`,
);
}
throw error;
}
const args = [
// can't directly spawn `jscodeshiftExecutable` due to https://github.com/facebook/jscodeshift/issues/424
jscodeshiftExecutable,
'--transform',
transformerPath,
...codemodFlags,
'--extensions',
'js,ts,jsx,tsx,json',
'--parser',
flags.parser || 'tsx',
'--ignore-pattern',
'**/node_modules/**',
'--ignore-pattern',
'**/*.css',
];
if (flags.dry) {
args.push('--dry');
}
if (flags.print) {
args.push('--print');
}
if (flags.jscodeshift) {
args.push(flags.jscodeshift);
}
if (flags.packageName) {
args.push(`--packageName=${flags.packageName}`);
}
args.push(...files);
// eslint-disable-next-line no-console -- debug information
console.log(`Executing command: jscodeshift ${args.join(' ')}`);
const jscodeshiftProcess = childProcess.spawnSync('node', args, { stdio: 'inherit' });
if (jscodeshiftProcess.error) {
throw jscodeshiftProcess.error;
}
}
const parseCssFilePaths = async (files) => {
const cssFiles = await Promise.all(
files.map(async (filePath) => {
const stat = await fs.stat(filePath);
if (stat.isDirectory()) {
return `${filePath}/**/*.css`;
}
if (filePath.endsWith('.css')) {
return filePath;
}
return null;
}),
);
return cssFiles.filter(Boolean);
};
async function runPostcssTransform(transform, files) {
// local postcss plugins are loaded through config files https://github.com/postcss/postcss-load-config/issues/17#issuecomment-253125559
const paths = [
path.resolve(__dirname, './src', `${transform}/postcss.config.js`),
path.resolve(__dirname, './', `${transform}/postcss.config.js`),
];
let configPath;
let error;
for (const item of paths) {
try {
// eslint-disable-next-line no-await-in-loop
await fs.stat(item);
error = undefined;
configPath = item;
break;
} catch (srcPathError) {
error = srcPathError;
continue;
}
}
if (error) {
// don't throw if the file is not found, postcss transform is optional
if (error?.code !== 'ENOENT') {
throw error;
}
} else {
const cssPaths = await parseCssFilePaths(files);
if (cssPaths.length > 0) {
const args = [
postcssExecutable,
...cssPaths,
'--config',
configPath,
'--replace',
'--verbose',
];
// eslint-disable-next-line no-console -- debug information
console.log(`Executing command: postcss ${args.join(' ')}`);
const postcssProcess = childProcess.spawnSync('node', args, { stdio: 'inherit' });
if (postcssProcess.error) {
throw postcssProcess.error;
}
}
}
}
function run(argv) {
const { codemod, paths, ...flags } = argv;
const files = paths.map((filePath) => path.resolve(filePath));
runJscodeshiftTransform(codemod, files, flags, argv._);
runPostcssTransform(codemod, files);
}
yargs
.command({
command: '$0 <codemod> <paths...>',
describe: 'Applies a `@mui/codemod` to the specified paths',
builder: (command) => {
return command
.positional('codemod', {
description: 'The name of the codemod',
type: 'string',
})
.positional('paths', {
array: true,
description: 'Paths forwarded to `jscodeshift`',
type: 'string',
})
.option('dry', {
description: 'dry run (no changes are made to files)',
default: false,
type: 'boolean',
})
.option('parser', {
description: 'which parser for jscodeshift to use',
default: 'tsx',
type: 'string',
})
.option('print', {
description: 'print transformed files to stdout, useful for development',
default: false,
type: 'boolean',
})
.option('jscodeshift', {
description: '(Advanced) Pass options directly to jscodeshift',
default: false,
type: 'string',
})
.option('packageName', {
description: 'The package name to look for in the import statements',
default: '@mui/material',
type: 'string',
});
},
handler: run,
})
.scriptName('npx @mui/codemod')
.example('$0 v4.0.0/theme-spacing-api src')
.example('$0 v5.0.0/component-rename-prop src -- --component=Grid --from=prop --to=newProp')
.help()
.parse();