Skip to content

Instantly share code, notes, and snippets.

@IlCallo
Last active August 12, 2022 13:27
Show Gist options
  • Save IlCallo/288ccdc2cac782854bb4d78783389fd5 to your computer and use it in GitHub Desktop.
Save IlCallo/288ccdc2cac782854bb4d78783389fd5 to your computer and use it in GitHub Desktop.
Run a "create" command on the CLI and programmatically answer it's prompts. Check out https://github.com/dreamonkey/cli-ghostwriter which has been derived from this example
import { spawn } from "node:child_process";
export type SupportedPackageManager = "npm" | "yarn" | "pnpm";
export const ACCEPT_DEFAULT = "ACCEPT_DEFAULT";
export const ENTER_KEY = "\n";
export const WHITESPACE_KEY = " ";
// Octals literals are banned into ESM
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Deprecated_octal#octal_escape_sequences
export const DOWN_KEY = "\u001b[B";
interface CreateParams {
packageManager: SupportedPackageManager;
packageName: string;
answersMap: Record<string, string | undefined>;
endingMarker: string;
enableLogs?: boolean;
}
export function cliCreate({
packageManager,
packageName,
answersMap,
endingMarker,
enableLogs,
}: CreateParams) {
enableLogs = enableLogs ?? false;
return new Promise<void>((resolve, reject) => {
const childProcess = spawn(packageManager, ["create", packageName], {
cwd: process.cwd(),
stdio: "pipe",
shell: true,
});
childProcess.stdout.setEncoding("utf8");
childProcess.stdout.on("data", (data: string) => {
if (data.includes(endingMarker)) {
childProcess.stdin.end();
}
enableLogs && console.log("prompt:", data);
for (const question in answersMap) {
if (data.includes(question)) {
const answer = answersMap[question];
enableLogs && console.log("answer:", answer);
// To accept the default value we just need to press enter,
// so we aren't going to write the answer
if (!!answer && answer !== ACCEPT_DEFAULT) {
childProcess.stdin.write(answer);
}
childProcess.stdin.write(ENTER_KEY);
// If we don't remove the question once used, we'd match the line showing
// the question after it has been answered too, generating erratic behavior
delete answersMap[question];
break;
}
}
});
childProcess.on("exit", (code) => {
console.log();
if (code) {
console.log(` Creation FAILED...`);
console.log();
reject();
} else {
resolve();
}
});
});
}
import { ACCEPT_DEFAULT, cliCreate, DOWN_KEY, WHITESPACE_KEY } from "./cli-create";
// Test this with `create-quasar`
const answersMap: Record<string, string | undefined> = {
"What would you like to build?": ACCEPT_DEFAULT, // App
"Project folder": "my-project-folder",
"Remove existing files and continue?": "y",
"Pick Quasar version": ACCEPT_DEFAULT, // Qv2
"Pick script type": DOWN_KEY, // TypeScript
"Pick Quasar App CLI variant": ACCEPT_DEFAULT, // Webpack
"Package name": "my-project",
"Project product name": "My Project",
"Project description": "",
Author: ACCEPT_DEFAULT,
"Pick a Vue component style": DOWN_KEY, // composition-setup
"Pick your CSS preprocessor": ACCEPT_DEFAULT, // SCSS
"Check the features needed for your project": `${DOWN_KEY}${WHITESPACE_KEY}${DOWN_KEY}${DOWN_KEY}${WHITESPACE_KEY}`, // checks: ESLint (default), Pinia, Axios
"Pick an ESLint preset": ACCEPT_DEFAULT, // prettier
"Install project dependencies?": DOWN_KEY, // No
};
await cliCreate({
packageManager: "pnpm",
packageName: "quasar",
answersMap,
endingMarker: "Enjoy! - Quasar Team",
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment