create-onetech-app
Version:
CLI to quickly set up React projects with popular templates and tools.
214 lines (213 loc) ⢠8.94 kB
JavaScript
import packagejson from "../../package.json" with { type: "json" };
import chalk from "chalk";
import { templateDir, userPath, userPathValidate } from "./file.js";
import path from "path";
import TechData from "../data/TechStack.js";
import { exec } from "child_process";
import prompt from "./promt.js";
// function for displaying commands and info
function help() {
console.log(`\nš¦ create-onetech-app using version: ${chalk.green(packagejson.version)}\n`);
console.log(`${chalk.bold("Usage:")}`);
console.log(` create-onetech-app ${chalk.cyan("<project-name>")} [options]\n`);
console.log(`${chalk.bold("Options:")}`);
console.log(` ${chalk.yellow("-v, -version")} Show the current version`);
console.log(` ${chalk.yellow("--verbose, --v")} Enable verbose output`);
console.log(` ${chalk.yellow("--template")} Choose a template (e.g., vite-react)`);
console.log(` ${chalk.yellow("--lang")} Set language (ts/js)`);
console.log(` ${chalk.yellow("--stacks")} Add stacks like tailwind, firebase`);
console.log(` ${chalk.yellow("-h, --help")} Show this help message\n`);
console.log(`${chalk.bold("Example:")}`);
console.log(` create-onetech-app my-app --template=vite --lang=ts --template=app-tw --verbose\n`);
}
/**
* Prompts the user to enter a valid project name (directory name).
*
* - Starts with an initial name provided via CLI.
* - Validates if the corresponding path is empty (or doesn't exist).
* - If the path is not empty, recursively prompts the user until
* an available directory name is provided.
* - Exits the process if the user provides no input.
*
* @param initial - The initial project name provided via CLI.
* @returns A valid (empty or non-existing) project directory name.
*/
async function getValidProjectName(initial) {
// store initial value in currentdir var
let currentDir = initial;
// recursive loop for validating currentdir
while (true) {
// if currentdir is undefined
if (currentDir === undefined) {
currentDir = await new prompt().projectname();
continue;
}
else {
// validate project name for cross platform to avoid dir/path issues 2nd parameter t which is toggle between string & boolean
const c = new prompt().validateProjectName(currentDir, true);
// if false
if (!c || currentDir.length === 0) {
// takes new promt and validate dir
currentDir = await new prompt().projectname();
continue;
}
// now it check path is empty or not. also it creates an dir if not exits
const p = await userPathValidate(userPath, currentDir);
if (p.isEmpty) {
return currentDir;
}
}
// no control over loop till condition succeed
}
}
// class commands
class commands {
// args are passed by super main function
constructor(args) {
this.args = args;
// Cmd cli is command line interface (only ref for CmdCli not acutal defination).
this.cmdCli = undefined;
// instance of prompt for taking inputs
this.prompt = new prompt();
}
// parseArgs it checks args if exits or take via promt
async parseArgs() {
// intialize Args var for void this.args to write always
const Args = this.args;
// check verbose cmd
const verbose = Args.includes("--v") || Args.includes("--verbose");
// check help cmd
const help = Args.includes("--h") || Args.includes("--help");
// version cmd
const version = Args.includes("-v") || Args.includes("-version");
// if help excute it
if (help) {
this.cmdCli = {
name: "help",
};
return verbose;
}
// if version shows current version
if (version) {
this.cmdCli = {
name: "version",
};
return verbose;
}
// verbose info
if (verbose)
console.log(chalk.greenBright("verbose enabled"));
// projectname: to identify via args
const projectname = Args
? Args.filter((arg) => !(arg.startsWith("--") || arg.startsWith("--")))[0]
: "";
// dir : getting correct and cross platform friendly dirname
const dir = await getValidProjectName(projectname);
// list _cmds: list of req var for main cmd
const _cmds = ["--base=", "--lang=", "--template="];
const techConfig = {
base: undefined,
lang: undefined,
template: undefined,
};
// iterations for get args from cmd if not make var as undefined
for (const cmd of _cmds) {
const key = cmd.replace(/^--|=$/g, "");
const found = Args.find((arg) => arg.startsWith(cmd));
techConfig[key] = found ? found.split("=")[1] : undefined;
}
// iterations for taking prompt from user, to get val of undefined var
for (const [key, value] of Object.entries(techConfig)) {
if (value === undefined) {
if (key === "base") {
techConfig["base"] = await this.prompt.projectBase();
}
const selectedBase = techConfig["base"];
const techMatch = TechData.find((b) => b.base === selectedBase);
if (selectedBase && techMatch) {
if (key === "lang") {
techConfig["lang"] =
selectedBase !== "flutter"
? await this.prompt.projectLang(techMatch.lang)
: "";
}
if (key === "template") {
techConfig["template"] = await this.prompt.projectTemplates(techMatch.templates);
}
}
}
}
// final config
const finalconfig = {
dir: path.join(dir),
...techConfig,
};
if (finalconfig.base === "flutter" &&
(finalconfig.lang === "js" || finalconfig.lang === "ts")) {
console.log(chalk.red("note: flutter is a framework of js."));
console.log("ref : " + chalk.grey("https://flutter.dev/learn"));
}
// excetution path to clone prebuilt templates based promts
let executionPath = path.join(templateDir, finalconfig.base, finalconfig.base === "flutter" ? "" : finalconfig.lang, finalconfig.template);
const onetech = path.join(templateDir, "..", "dist", "core", "onetech.js");
// cli
this.cmdCli = {
name: "main",
cmd: `node ${onetech} ${finalconfig.dir} ${executionPath} `,
};
return verbose;
}
// Execute CLI command if matched
async run(verbose = false) {
// cmdcli is undefined fallback case
if (!this.cmdCli) {
console.log(chalk.red("No command found!\n"));
help();
return;
}
// accessing var
const { name, cmd } = this.cmdCli;
if (verbose) {
console.log(chalk.blueBright("ā Running CLI command:"), name);
}
// based on names = version | help | main excutes
switch (name) {
case "version":
console.log(`Using version: ${chalk.green(packagejson.version)}`);
break;
case "help":
help();
break;
case "main":
if (!cmd) {
console.log(chalk.red("Invalid executor path or project directory."));
return;
}
if (verbose) {
console.log(chalk.cyan("ā Executor path:"), cmd);
}
await exec(cmd, (error, stdout, stderr) => {
// error: If command fails to run or exits with non-zero code
if (error) {
console.error(chalk.red(`Execution error: ${error.message}`));
return;
}
// stdout: Normal output of the command
if (stdout) {
console.log("\n\n" + stdout);
}
// stderr: Error output of the command
if (stderr) {
console.error(chalk.yellow("ā Script Error:\n") + stderr);
}
});
break;
// fallback
default:
console.log(chalk.red(`Unknown command: ${name}`));
help();
break;
}
}
}
export default commands;