decaf-transform
Version:
Tool to decaf & transform a CoffeeScript React project using react dom factories to jsx
217 lines (195 loc) • 7.14 kB
JavaScript
const Runner = require('jscodeshift/src/Runner')
const prettier = require('prettier')
const CLIEngine = require('eslint').CLIEngine
const decaffeinate = require('decaffeinate')
const minimist = require('minimist')
const {flattenDeep} = require('lodash')
const {readdir, readFile, stat, unlink, writeFile} = require('fs-extra')
;(async function main() {
try {
const {
help,
replace,
decaffeinate,
removeCoffeeFiles,
transform,
eslintFix,
prettify,
path,
skipSubdirectories,
} = parseArgs()
const customized = replace || decaffeinate || removeCoffeeFiles || transform || eslintFix || prettify
if (help) return showHelp()
const workingDirectory = `${process.cwd()}${path ? `/${path}` : ''}`
const isFile = workingDirectory.endsWith('.coffee')
// Initial batch of files
const files = isFile
? [workingDirectory]
: await fetchAllFiles((await readdir(workingDirectory)).map(dir => `${path ? `${path}/` : ''}${dir}`), {
skipSubdirectories,
})
const coffeeFiles = files.filter(file => file.endsWith('.coffee'))
const replacementCandidates = files.filter(file => file.match(/\.(coffee|md)$/))
if (replace || !customized) await replaceHardReferencesToCoffee(replacementCandidates) // r
if (decaffeinate || !customized) await decaffeinateAllFiles(coffeeFiles) // d
if (removeCoffeeFiles || !customized) await removeAllCoffeeFiles(coffeeFiles) // m
// Files could change
const jsFiles = (replace || decaffeinate || removeAllCoffeeFiles || !customized
? isFile
? [workingDirectory.replace('.coffee', '.js')]
: await fetchAllFiles((await readdir(workingDirectory)).map(dir => `${path ? `${path}/` : ''}${dir}`), {
skipSubdirectories,
})
: files
).filter(file => file.endsWith('.js'))
if (transform || !customized) await runTransforms(jsFiles) // t
if (eslintFix || !customized) eslintFixFiles(jsFiles) // e
if (prettify || !customized) await prettifyFiles(jsFiles) // p
} catch (e) {
reportErrors(e)
}
})()
async function fetchAllFiles(potentialDirectories, {skipSubdirectories}) {
async function getFiles(dir, fileList = []) {
const items = await readdir(dir)
for (const item of items) {
const itemPath = `${dir}${item}`
if ((await stat(itemPath)).isDirectory()) {
fileList = await getFiles(`${itemPath}/`, fileList)
} else {
fileList.push(itemPath)
}
}
return fileList
}
const allFiles = []
for (const directory of potentialDirectories) {
if ((await stat(directory)).isDirectory() && !skipSubdirectories) {
allFiles.push(await getFiles(`${directory}/`))
} else {
allFiles.push(directory)
}
}
return flattenDeep(allFiles)
}
async function replaceHardReferencesToCoffee(files) {
const totalCount = files.length
console.log(`Patching all ${totalCount} files`)
let progress = 0
let percent = 0
for (const file of files) {
const fileSource = (await readFile(file, 'utf8')).split('\n')
const patchedSource = fileSource
.map(line => {
if (line.includes('isCoffee')) return line // The one of case it needs to stay
return line.replace('.coffee', '')
})
.join('\n')
await writeFile(file, patchedSource)
const newPercent = Math.floor((++progress / totalCount) * 100)
if (percent !== newPercent) {
percent = newPercent
console.log(`Patched ${percent}%`)
}
}
console.log('Patching complete')
}
async function decaffeinateAllFiles(coffeeFiles) {
const totalCount = coffeeFiles.length
console.log(`Decaffeinating all ${totalCount} files`)
let progress = 0
let percent = 0
for (const file of coffeeFiles) {
const converted = decaffeinate.convert(await readFile(file, 'utf8'))
await writeFile(file.replace('.coffee', '.js'), converted.code)
const newPercent = Math.floor((++progress / totalCount) * 100)
if (percent !== newPercent) {
percent = newPercent
console.log(`Decaffeinated ${percent}%`)
}
}
console.log('Decaffeination complete')
}
async function removeAllCoffeeFiles(coffeeFiles) {
console.log(`Removing all ${coffeeFiles.length} coffee files`)
for (const file of coffeeFiles) {
await unlink(file)
}
}
async function runTransforms(files) {
console.log('Running codeshifts')
await Runner.run(`${__dirname}/helpers/codeshifts.js`, files, {})
console.log('Codeshifts complete')
}
function eslintFixFiles(files) {
const cli = new CLIEngine({
configFile: `${__dirname}/.eslintrc`,
fix: true,
})
console.log('Running eslint --fix on all files')
const report = cli.executeOnFiles(files)
CLIEngine.outputFixes(report)
console.log('All files eslint --fix ed')
}
async function prettifyFiles(files) {
console.log('Running Prettier on all files')
for (const file of files) {
const formattedFile = prettier.format(await readFile(file, 'utf8'), {
arrowParens: 'avoid',
bracketSpacing: false,
parser: 'babel',
printWidth: 120,
semi: false,
singleQuote: true,
tabWidth: 2,
trailingComma: 'es5',
})
await writeFile(file, formattedFile)
}
console.log('All files Prettified')
}
function parseArgs() {
const {
h: help,
r: replace,
d: decaffeinate,
m: removeCoffeeFiles,
t: transform,
e: eslintFix,
p: prettify,
path,
'skip-subdirectories': skipSubdirectories,
} = minimist(process.argv.slice(2))
return {help, replace, decaffeinate, removeCoffeeFiles, transform, eslintFix, prettify, path, skipSubdirectories}
}
function showHelp() {
console.log(`
Decaffeinate & Transform tool
By default, all the steps will be run in the current working directory.
Additionally, we can specify a path with --path relative/path/to/dir.
If any other flags are set, the tool will only run the steps specified.
Usage: npx decaf-transform [options]
-h shows this help
-r replaces lines of code that say *.coffee to no extension
-d decaffeinates coffee files & generates new js files
-m removes coffee files
-t runs transform (from js -> jsx)
-e runs eslint --fix
-p runs prettier
--path relative/path specify a specific directory/file to work on
--skip-subdirectories Will only run on coffee files in the directory provided
Examples:
npx decaf-transform --path modules/projects to run everything against projects
npx decaf-transform -p --path skeletor runs prettier just in skeletor
npx decaf-transform -rd to run first two steps (can compare
new js files to old coffee)
`)
}
function reportErrors(e) {
console.error(`
Report to Mohonri:
Args: ${process.argv}
Message: ${e}
`)
}