Skip to content

Instantly share code, notes, and snippets.

@pyronaur
Last active January 26, 2023 10:55
Show Gist options
  • Save pyronaur/60e7ab68e09fdfa4e8b1ea7f22d76beb to your computer and use it in GitHub Desktop.
Save pyronaur/60e7ab68e09fdfa4e8b1ea7f22d76beb to your computer and use it in GitHub Desktop.
Auto-fix all changed files in the Jetpack Monorepo
#!/usr/bin/env zx
// Pass --verbose to see the full output
$.verbose = argv.verbose;
function isPHP(filename) {
return filename.match(/\.php$/);
}
function isJS(filename) {
return filename.match(/\.(js|jsx|cjs|mjs|ts|tsx|svelte|json|json5)$/);
}
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) => isPHP(file) || isJS(file);
const isDirectory = fs.statSync(target).isDirectory();
const files = [];
if (isDirectory) {
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(isJS) : [],
php: scope == 'any' || scope === 'php' ? files.filter(isPHP) : [],
}
}
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.green.bold("Fixed")} ${file.replace(`${cwd}/`, '')}`);
}
}
async function fixJS(file) {
const result = await $`pnpm run -w lint-file ${file} --fix && ${gitDirectory}/tools/js-tools/node_modules/.bin/prettier --ignore-path .eslintignore --write ${file}`;
logResult(file, result);
}
async function fixPHP(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);
}
}
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.all) {
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 target = argv._[0] || cwd;
const files = await getFilesToFix(target, gitDirectory, scope);
const count = Object.values(files).flat().length;
if (count > 0) {
if (argv.all) {
console.log(`Fixing ${count} files in "${chalk.dim(gitDirectory)}"...\n`);
} else if (count > 1) {
console.log(`Fixing ${count} files in "${target}"...\n`);
} else {
const file = Object.values(files).flat().pop();
console.log(`Fixing "${chalk.dim(file)}"...\n`)
}
const tasks = [
...files.php.map(fixPHP),
...files.js.map(fixJS)
]
await Promise.all(tasks);
} else {
console.log(chalk.green.bold("Nothing to fix"));
if (await confirm("Do you want to reformat all files?")) {
await reformatEverything(gitDirectory, 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