Created
February 7, 2023 16:48
-
-
Save pyronaur/8f5b4608344cefc5346ca8ee096982ac to your computer and use it in GitHub Desktop.
Monorepo fixer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env zx | |
// Pass --verbose to see the full output | |
$.verbose = argv.verbose; | |
const php = { | |
is: (file) => file.match(/\.php$/), | |
fix: async (file) => { | |
const result = await nothrow($`pnpm run -w php:autofix ${file}`); | |
// php:autofix outputs the fixes to stderr for some reason. | |
// Hide it unless --verbose is passed | |
if (argv.verbose !== true) { | |
logResult(file, { | |
stderr: null, | |
...result | |
}); | |
} else { | |
logResult(file, result); | |
} | |
}, | |
lint: async (file) => { | |
const result = await nothrow($`pnpm run -w php:lint ${file}`); | |
console.log("\n\n"); | |
console.log("🎨 " + chalk.cyan.bold("Linting") + " " + chalk.yellow.bold(file.replace(`${cwd}/`, ''))) | |
const errorPhrases = [ | |
'FOUND', | |
'ERROR', | |
] | |
if (errorPhrases.some(phrase => result.toString().includes(phrase))) { | |
console.log(chalk.red.bold("Found issues:")); | |
console.log(result.toString()); | |
console.log("\n"); | |
} else { | |
console.log(chalk.green.bold(`Looks good!`)); | |
if (argv.verbose) { | |
console.log(result.toString()); | |
} | |
} | |
console.log("\n"); | |
} | |
} | |
const js = { | |
is: (file) => file.match(/\.(js|jsx|cjs|mjs|ts|tsx|svelte)$/), | |
fix: async (file) => { | |
const eslintIgnorePath = fs.existsSync(`${cwd}/.eslintignore`) ? `${cwd}/.eslintignore` : `${gitDirectory}/.eslintignore`; | |
const result = await $`pnpm run -w lint-file ${file} --fix && ${gitDirectory}/tools/js-tools/node_modules/.bin/prettier --ignore-path ${eslintIgnorePath} --write ${file}`; | |
logResult(file, result); | |
}, | |
lint: async (file) => { | |
const eslintIgnorePath = fs.existsSync(`${cwd}/.eslintignore`) ? `${cwd}/.eslintignore` : `${gitDirectory}/.eslintignore`; | |
const result = await nothrow($`pnpm run -w lint-file ${file} && ${gitDirectory}/tools/js-tools/node_modules/.bin/prettier --ignore-path ${eslintIgnorePath} --check ${file}`); | |
console.log(chalk.cyan.bold("Linting") + " " + chalk.bold(file.replace(`${cwd}/`, ''))) | |
const errorPhrases = [ | |
'✖', | |
'error', | |
'forgot to run prettier', | |
] | |
if (errorPhrases.some(phrase => result.toString().includes(phrase))) { | |
console.log(chalk.red.bold("Found issues:")); | |
console.log(result.toString()); | |
console.log("\n"); | |
} else { | |
console.log(chalk.green.bold(`Looks good!`)); | |
if (argv.verbose) { | |
console.log(result.toString()); | |
} | |
} | |
console.log("\n"); | |
} | |
} | |
async function getFilesToFix(target, rootPath, scope = 'any') { | |
target = path.resolve(target); | |
if (!fs.existsSync(target)) { | |
console.error(chalk.red.bold(`"${target}" doesn't exist`)); | |
process.exit(); | |
} | |
const isFixableFile = (file) => php.is(file) || js.is(file); | |
const isDirectory = fs.statSync(target).isDirectory(); | |
const files = []; | |
if (isDirectory) { | |
if (argv.all) { | |
const allFiles = await globby([`${target}/**/*`], { | |
onlyFiles: true, | |
ignoreFiles: [".eslintignore", ".gitignore", ".prettierignore", `${gitDirectory}/.eslintignore`, `${gitDirectory}/.gitignore`, `${gitDirectory}/.prettierignore}`] | |
}); | |
files.push(...allFiles.filter(isFixableFile)); | |
} else { | |
const changedFileList = await $`git diff --name-only --diff-filter=ACMRTUXB HEAD;`; | |
const changedFiles = | |
changedFileList.toString().split('\n') | |
.map(f => path.resolve(rootPath, f)) | |
.filter(isFixableFile) | |
.filter(file => file.startsWith(target)) | |
files.push(...changedFiles); | |
} | |
} else { | |
files.push(target); | |
} | |
return { | |
js: scope == 'any' || scope === 'js' ? files.filter(js.is) : [], | |
php: scope == 'any' || scope === 'php' ? files.filter(php.is) : [], | |
} | |
} | |
function logResult(file, result) { | |
if (result.stderr && !result.stdout.toString().includes("No fixable errors were found")) { | |
console.log(chalk.bold.red(`Error reported fixing "${file.replace(`${cwd}/`, '')}"`)); | |
console.log(result.stdout); | |
console.log(chalk.dim("--------------------------------")); | |
console.log(chalk.bold.red(`exitCode "${result.exitCode}" received:\n`)); | |
console.error(result.stderr); | |
console.log(chalk.dim("--------------------------------")); | |
} else { | |
console.log(`${chalk.magenta.bold("Fixed")} ${chalk.yellow.bold(file.replace(`${cwd}/`, ''))}`); | |
} | |
} | |
async function confirm(q, defaultAnswer = "n") { | |
let yes_no = `(y/N)`; | |
if (defaultAnswer === "y") { | |
yes_no = `(Y/n)`; | |
} | |
let answer = await question(`${q} ${yes_no} `); | |
if (!answer) { | |
answer = defaultAnswer; | |
} | |
return "y" === answer; | |
} | |
async function selection(options, selectionQuestion) { | |
let result; | |
options.forEach((opt, index) => { | |
console.log(`> ${chalk.bold(index + 1)}: ${opt} `) | |
}) | |
const selected = await question(selectionQuestion + " (default: 1): \n") || 1; | |
result = options[selected - 1]; | |
return result; | |
} | |
async function reformatEverything(gitDirectory, target) { | |
const options = [ | |
`Fix all files in "${target}"`, | |
'Fix all files in the repo', | |
] | |
const selected = await selection(options, 'What do you want to do?'); | |
// This is going to be slow. | |
// Show the verbose output otherwise it feels like the command is stuck | |
$.verbose = true; | |
// Fix all files in the repo | |
if (selected === options[0]) { | |
console.log(chalk.bold.green("Fixing all files in the repo...")); | |
cd(gitDirectory); | |
await $`pnpm run -w php:autofix`; | |
await $`pnpm run -w reformat-files`; | |
} | |
// Fix all files in the current directory | |
if (selected === options[1]) { | |
console.log(chalk.bold.green("Fixing all files in the current directory...")); | |
let eslintIgnorePath = path.resolve(target, '.eslintignore'); | |
if (!fs.existsSync(eslintIgnorePath)) { | |
eslintIgnorePath = path.resolve(gitDirectory, '.eslintignore'); | |
} | |
await $`pnpm run -w php:autofix ${target}`; | |
await $`${gitDirectory}/tools/js-tools/node_modules/.bin/prettier --ignore-path ${eslintIgnorePath} --write ${target}`; | |
} | |
if (!selected) { | |
console.log(chalk.dim(`Couldn't find an option matching what you specified.`)); | |
console.log(chalk.bold.red("Nothing to do.")); | |
process.exit(); | |
} | |
} | |
/** | |
* | |
* Run | |
* | |
*/ | |
/** | |
* --all | |
* look for all changed files in the repo | |
*/ | |
const gitDirectory = (await $`git rev-parse --show-toplevel`).toString().trim(); | |
if (argv.root) { | |
await cd(gitDirectory); | |
} | |
const cwd = (await $`pwd`).toString().trim(); | |
/** | |
* --js | --php | |
* only fix specific file types | |
*/ | |
let scope = 'any'; | |
if (argv.js) { | |
scope = 'js'; | |
} | |
if (argv.php) { | |
scope = 'php'; | |
} | |
const task = ['lint', 'fix'].includes(argv._[0]) ? argv._.shift() : 'all'; | |
const target = argv._.length > 0 ? argv._.shift() : cwd; | |
const files = await getFilesToFix(target, gitDirectory, scope); | |
const count = Object.values(files).flat().length; | |
if (count > 0) { | |
if (argv.root) { | |
console.log(`Found ${count} files in "${chalk.dim(gitDirectory)}"...\n`); | |
} else if (count > 1) { | |
console.log(`Found ${count} files in "${target}"...\n`); | |
} else { | |
const file = Object.values(files).flat().pop(); | |
console.log(`Found "${chalk.dim(file)}"...\n`) | |
} | |
if (task === "all") { | |
const fix = [ | |
...files.php.map(php.fix), | |
...files.js.map(js.fix) | |
] | |
console.log(chalk.bold.magenta("\n\nFixing...")); | |
await Promise.all(fix); | |
const lint = [ | |
...files.php.map(php.lint), | |
...files.js.map(js.lint) | |
] | |
console.log(chalk.bold.cyan("\n\nLinting...")); | |
await Promise.all(lint); | |
} else { | |
const tasks = [ | |
...files.php.map(php[task]), | |
...files.js.map(js[task]) | |
] | |
await Promise.all(tasks); | |
} | |
} else if (task === 'fix') { | |
console.log(chalk.green.bold("Nothing to fix")); | |
if (await confirm("Do you want to reformat all files?")) { | |
await reformatEverything(gitDirectory, target); | |
} | |
} else { | |
console.log(chalk.yellow("Nothing to lint or fix in", target)); | |
} | |
console.log(chalk.bold.green("Done!")); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment