Skip to content

Commit

Permalink
Add initial CLI which prompts user for starter code options (#1)
Browse files Browse the repository at this point in the history
* Add initial CLI which prompts user for starter code options

* Refactor initial CLI
  • Loading branch information
alexguo8 authored Mar 8, 2021
1 parent 657cb96 commit 8501680
Show file tree
Hide file tree
Showing 7 changed files with 513 additions and 11 deletions.
4 changes: 3 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ module.exports = {
"prettier",
"plugin:prettier/recommended",
],
rules: {},
rules: {
"no-console": "off",
},
};
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,6 @@ dist

# TernJS port file
.tern-port

# Compiled TS
bin/
183 changes: 183 additions & 0 deletions cli/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import boxen from "boxen";
import chalk from "chalk";
import figlet from "figlet";
import inquirer from "inquirer";
import yargs from "yargs/yargs";

type CommandLineArgs = Array<string>;

type Options = {
[x: string]: unknown;
backend?: string;
api?: string;
database?: string;
auth?: boolean;
_?: (string | number)[];
$0?: string;
};

const OPTIONS = {
backend: {
id: "b",
description: "Backend language",
message: "Which backend language would you like?",
choices: [
{ name: "TypeScript (Node/Express)", value: "typescript" },
{ name: "Python (Flask)", value: "python" },
],
},
api: {
id: "a",
description: "API type",
message: "Which API would you like?",
choices: [
{ name: "REST", value: "rest" },
{ name: "GraphQL", value: "graphql" },
],
},
database: {
id: "d",
description: "Database",
message: "Which database would you like?",
choices: [
{ name: "PostgreSQL", value: "postgresql" },
{ name: "MongoDB", value: "mongodb" },
],
},
auth: {
id: "au",
description: "Include built-in auth features",
message: "Would you like built-in auth features?",
},
};

const parseArguments = (args: CommandLineArgs) => {
const { argv } = yargs(args.slice(2)).options({
backend: {
alias: OPTIONS.backend.id,
type: "string",
description: OPTIONS.backend.description,
choices: OPTIONS.backend.choices.map((choice) => choice.value),
},
api: {
alias: OPTIONS.api.id,
type: "string",
description: OPTIONS.api.description,
choices: OPTIONS.api.choices.map((choice) => choice.value),
},
database: {
alias: OPTIONS.database.id,
type: "string",
description: OPTIONS.api.description,
choices: OPTIONS.database.choices.map((choice) => choice.value),
},
auth: {
alias: OPTIONS.auth.id,
type: "boolean",
description: OPTIONS.auth.description,
},
});

return argv;
};

const promptOptions = async (options: Options) => {
const prompts = [];
if (!options.backend) {
prompts.push({
type: "list",
name: "backend",
message: OPTIONS.backend.message,
choices: OPTIONS.backend.choices,
});
}

if (!options.api) {
prompts.push({
type: "list",
name: "api",
message: OPTIONS.api.message,
choices: OPTIONS.api.choices,
});
}

if (!options.database) {
prompts.push({
type: "list",
name: "database",
message: OPTIONS.database.message,
choices: OPTIONS.database.choices,
});
}

if (!options.auth) {
prompts.push({
type: "confirm",
name: "auth",
message: OPTIONS.auth.message,
default: false,
});
}

const answers = await inquirer.prompt(prompts);

return {
backend: options.backend || answers.backend,
api: options.api || answers.api,
database: options.database || answers.database,
auth: options.auth || answers.auth,
};
};

const confirmPrompt = async (options: Options) => {
const backendName = OPTIONS.backend.choices.find(
(choice) => choice.value === options.backend,
)?.name;
const apiName = OPTIONS.api.choices.find(
(choice) => choice.value === options.api,
)?.name;
const databaseName = OPTIONS.database.choices.find(
(choice) => choice.value === options.database,
)?.name;

const message =
`You have chosen to create a ${backendName} app with a ` +
`${apiName} API, ${databaseName} database, and ${
options.auth ? "" : "no "
}built-in auth. Please confirm:`;

const prompt = {
type: "confirm",
name: "confirm",
message,
default: false,
};
const { confirm } = await inquirer.prompt([prompt]);
return confirm;
};

const cli = async (args: CommandLineArgs) => {
console.log(
boxen(
chalk.bold(
figlet.textSync("create-bp-app", { horizontalLayout: "full" }),
),
{
padding: 1,
margin: 1,
borderStyle: "double",
borderColor: "blue",
},
),
);
let options: Options = parseArguments(args);
options = await promptOptions(options);
const confirm = await confirmPrompt(options);
if (confirm) {
console.log(chalk.green.bold("Confirmed. Creating blueprint app..."));
} else {
console.log(chalk.red.bold("Blueprint app creation has been cancelled."));
}
};

export default cli;
4 changes: 3 additions & 1 deletion index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
console.log("Hello");
import cli from "./cli";

cli(process.argv);
14 changes: 12 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@
"scripts": {
"dev": "ts-node index.ts",
"lint": "eslint . --ext .ts,.js",
"lint-fix": "eslint . --ext .ts,.js --fix && prettier --write **/*.ts **/*.js"
"lint-fix": "eslint . --ext .ts,.js --fix && prettier --write **/*.ts **/*.js",
"prod": "tsc -p . && node bin/index.js"
},
"devDependencies": {
"@types/figlet": "^1.2.1",
"@types/inquirer": "^7.3.1",
"@types/node": "^14.14.31",
"@types/yargs": "^16.0.0",
"@typescript-eslint/eslint-plugin": "^4.15.2",
"@typescript-eslint/parser": "^4.15.2",
"eslint": "^7.20.0",
Expand All @@ -24,5 +28,11 @@
"ts-node": "^9.1.1",
"typescript": "^4.2.2"
},
"dependencies": {}
"dependencies": {
"boxen": "^5.0.0",
"chalk": "^4.1.0",
"figlet": "^1.5.0",
"inquirer": "^8.0.0",
"yargs": "^16.2.0"
}
}
4 changes: 2 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./", /* Redirect output structure to the directory. */
"outDir": "./bin", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
Expand Down Expand Up @@ -48,7 +48,7 @@
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
"types": ["node"], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
Expand Down
Loading

0 comments on commit 8501680

Please sign in to comment.