plop
Version:
Micro-generator framework that makes it easy for an entire team to create files with a level of uniformity
176 lines (163 loc) • 5.51 kB
JavaScript
import ora from "ora";
import path from "node:path";
import Liftoff from "liftoff";
import minimist from "minimist";
import v8flags from "v8flags";
import interpret from "interpret";
import chalk from "chalk";
const args = process.argv.slice(2);
const argv = minimist(args);
import nodePlop from "node-plop";
import * as out from "./console-out.js";
import { combineBypassData } from "./bypass.js";
import { getBypassAndGenerator, handleArgFlags } from "./input-processing.js";
const Plop = new Liftoff({
name: "plop",
extensions: interpret.jsVariants,
v8flags: v8flags,
});
const isInJest = process.env.NODE_ENV === "test";
const progressSpinner = ora({
// Default is stderr
stream: isInJest ? process.stdout : process.stderr,
isEnabled: !isInJest && argv.progress !== false,
});
/**
* The function to pass as the second argument to `Plop.execute`
* @param env - This is passed implicitly
* @param _ - Passed implicitly. Not needed, but allows for `passArgsBeforeDashes` to be explicitly passed
* @param passArgsBeforeDashes - An opt-in `true` boolean that will allow merging of plop CLI API and generator API
* @example
* Plop.execute(env => run(env, undefined, true))
*
* !!!!!! WARNING !!!!!!
* One of the reasons we default generator arguments as anything past `--` is a few reasons:
* Primarily that there may be name-spacing issues when combining the arg order and named arg passing
*/
async function run(env, _, passArgsBeforeDashes) {
const plopfilePath = env.configPath;
// handle basic argument flags like --help, --version, etc
handleArgFlags(env);
// use base path from argv or env if any is present, otherwise set it to the plopfile directory
const destBasePath = argv.dest || env.dest;
let plop;
try {
plop = await nodePlop(plopfilePath, {
destBasePath: destBasePath ? path.resolve(destBasePath) : undefined,
force: argv.force === true || argv.f === true || false,
});
} catch (e) {
console.error(
chalk.red("[PLOP] ") + "Something went wrong with reading your plop file",
e,
);
return;
}
const generators = plop.getGeneratorList();
const generatorNames = generators.map((v) => v.name);
const { generatorName, bypassArr, plopArgV } = getBypassAndGenerator(
plop,
passArgsBeforeDashes,
);
// look up a generator and run it with calculated bypass data
const runGeneratorByName = (name) => {
const generator = plop.getGenerator(name);
const bypassData = combineBypassData(generator, bypassArr, plopArgV);
doThePlop(generator, bypassData);
};
// hmmmm, couldn't identify a generator in the user's input
if (!generators.length) {
// no generators?! there's clearly something wrong here
console.error(chalk.red("[PLOP] ") + "No generator found in plopfile");
process.exit(1);
} else if (!generatorName && generators.length === 1) {
// only one generator in this plopfile... let's assume they
// want to run that one!
runGeneratorByName(generatorNames[0]);
} else if (!generatorName && generators.length > 1 && !bypassArr.length) {
// more than one generator? we'll have to ask the user which
// one they want to run.
out
.chooseOptionFromList(generators, plop.getWelcomeMessage())
.then(runGeneratorByName)
.catch((err) => {
console.error(
chalk.red("[PLOP] ") +
"Something went wrong with selecting a generator",
err,
);
});
} else if (generatorNames.includes(generatorName)) {
// we have found the generator, run it!
runGeneratorByName(generatorName);
} else {
// we just can't make sense of your input... sorry :-(
const fuzzyGenName = (generatorName + " " + args.join(" ")).trim();
console.error(
chalk.red("[PLOP] ") +
'Could not find a generator for "' +
fuzzyGenName +
'"',
);
process.exit(1);
}
return plop;
}
/////
// everybody to the plop!
//
function doThePlop(generator, bypassArr) {
let failedActions = false;
generator
.runPrompts(bypassArr)
.then(async (answers) => {
return answers;
})
.then((answers) => {
const noMap = argv["show-type-names"] || argv.t;
const onComment = (msg) => {
progressSpinner.info(msg);
progressSpinner.start();
};
const onSuccess = (change) => {
let line = "";
if (change.type) {
line += ` ${out.typeMap(change.type, noMap)}`;
}
if (change.path) {
line += ` ${change.path}`;
}
progressSpinner.succeed(line);
progressSpinner.start();
};
const onFailure = (fail) => {
let line = "";
if (fail.type) {
line += ` ${out.typeMap(fail.type, noMap)}`;
}
if (fail.path) {
line += ` ${fail.path}`;
}
const errMsg = fail.error || fail.message;
if (errMsg) {
line += ` ${errMsg}`;
}
progressSpinner.fail(line);
failedActions = true;
progressSpinner.start();
};
progressSpinner.start();
return generator
.runActions(answers, { onSuccess, onFailure, onComment })
.then(() => {
progressSpinner.stop();
if (failedActions) process.exit(1);
});
})
.catch(function (err) {
console.error(chalk.red("[ERROR]"), err.message);
process.exit(1);
});
}
export { Plop, run, progressSpinner };