@expressots/cli
Version:
Expressots CLI - modern, fast, lightweight nodejs web framework (@cli)
237 lines (236 loc) • 9.8 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.projectForm = void 0;
const chalk_1 = __importDefault(require("chalk"));
const cli_progress_1 = require("cli-progress");
const degit_1 = __importDefault(require("degit"));
const inquirer_1 = __importDefault(require("inquirer"));
const node_child_process_1 = require("node:child_process");
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const cli_1 = require("../cli");
const center_text_1 = require("../utils/center-text");
const change_package_info_1 = require("../utils/change-package-info");
const cli_ui_1 = require("../utils/cli-ui");
async function packageManagerInstall({ packageManager, directory, progressBar, }) {
const command = process.platform === "win32" ? `${packageManager}.cmd` : packageManager;
const args = ["install", "--silent"];
if (packageManager === "yarn") {
args.push("--ignore-engines");
args.splice(args.indexOf("--prefer-offline"), 1);
}
return new Promise((resolve, reject) => {
const installProcess = (0, node_child_process_1.spawn)(command, args, {
cwd: directory,
shell: true,
timeout: 600000,
});
// Simulate incremental progress
let progress = 0;
const interval = setInterval(() => {
if (progress < 90) {
progress += 5;
progressBar.update(progress);
}
}, 1000);
// Handle stdout for meaningful output or progress feedback
installProcess.stdout?.on("data", (data) => {
const output = data.toString().trim();
// Remove all data from || to the end of the line
const cleanedOutput = output.replace(/\|\|.*$/g, "");
// Match and handle npm-specific progress
const npmProgressMatch = cleanedOutput.match(/\[(\d+)\/(\d+)\] (?:npm )?([\w\s]+)\.{3}/);
if (npmProgressMatch) {
const [, current, total, task] = npmProgressMatch;
progress = Math.round((parseInt(current) / parseInt(total)) * 100);
progressBar.update(progress, { doing: task });
}
else {
// Update "task" without changing the progress
progressBar.update(progress, { doing: cleanedOutput });
}
});
// Handle errors
installProcess.on("error", (error) => {
clearInterval(interval); // Stop interval on error
progressBar.stop();
reject(new Error(`Failed to start subprocess: ${error.message}`));
});
// Finalize progress on close
installProcess.on("close", (code) => {
clearInterval(interval); // Stop interval when the process ends
if (code === 0) {
progressBar.update(100, { doing: "Complete!" }); // Finalize progress
progressBar.stop();
resolve("Installation Done!");
}
else {
progressBar.stop();
reject(new Error(`${packageManager} install exited with code ${code}`));
}
});
});
}
async function checkIfPackageManagerExists(packageManager) {
try {
(0, node_child_process_1.execSync)(`${packageManager} --version`);
return true;
}
catch (error) {
(0, cli_ui_1.printError)("Package manager not found!", packageManager);
process.exit(1);
}
}
function renameEnvFile(directory) {
try {
const envExamplePath = node_path_1.default.join(directory, ".env.example");
const envPath = node_path_1.default.join(directory, ".env");
if (!node_fs_1.default.existsSync(envExamplePath)) {
throw new Error(`File not found: ${envExamplePath}`);
}
node_fs_1.default.renameSync(envExamplePath, envPath);
}
catch (error) {
(0, cli_ui_1.printError)("Error renaming .env.example file", ".env.example to .env");
process.exit(1);
}
}
var Template;
(function (Template) {
Template["nonopinionated"] = "Non-Opinionated :: Start with a clean slate and build your project from scratch.";
Template["opinionated"] = "Opinionated :: Automatically scaffolds resources into a preset project structure. (Recommended)";
Template["micro"] = "Micro :: A minimalistic template for building micro api's.";
})(Template || (Template = {}));
const projectForm = async (projectName, args) => {
let answer;
const [packageManager, template, directory] = args;
if (packageManager && template) {
answer = {
name: projectName,
packageManager: packageManager,
template: Template[template],
confirm: true,
};
}
else {
answer = await inquirer_1.default.prompt([
{
type: "input",
name: "name",
message: "Project name",
default: projectName,
transformer: (input) => {
return chalk_1.default.yellow(chalk_1.default.bold(input));
},
},
{
type: "list",
name: "packageManager",
message: "Package manager",
choices: ["npm", "yarn", "pnpm", "bun"],
},
{
type: "list",
name: "template",
message: "Select a template",
choices: [
`Opinionated :: Automatically scaffolds resources into a preset project structure. (${chalk_1.default.yellow("Recommended")})`,
"NonOpinionated :: Allows users to choose where to scaffold resources, offering flexible project organization.",
"Micro :: A minimalistic template for building micro api's.",
],
},
{
type: "confirm",
name: "confirm",
message: "Do you want to create this project?",
default: true,
},
]);
}
if (directory) {
if (!node_fs_1.default.existsSync(node_path_1.default.join(directory, answer.name))) {
answer.name = node_path_1.default.join(directory, answer.name);
}
else {
(0, cli_ui_1.printError)("Directory already exists", directory);
process.exit(1);
}
}
// Hashmap of templates and their directories
const templates = {
NonOpinionated: "non_opinionated",
Opinionated: "opinionated",
Micro: "micro",
};
if (answer.confirm) {
// Check if package manager is bun and OS is Windows
if (answer.packageManager === "bun" && process.platform === "win32") {
(0, cli_ui_1.printError)("bun is not supported on Windows. Please use", "npm, yarn or pnpm");
process.exit(1);
}
await checkIfPackageManagerExists(answer.packageManager);
console.log("\n");
const progressBar = new cli_progress_1.SingleBar({
format: "Progress |" +
chalk_1.default.green("{bar}") +
"| {percentage}% || {doing}",
hideCursor: true,
}, cli_progress_1.Presets.rect);
progressBar.start(100, 0, {
doing: "Cloning project",
});
const [_, template] = answer.template.match(/(.*) ::/);
const repo = `expressots/templates/${templates[template]}#${cli_1.BUNDLE_VERSION}`;
try {
const emitter = (0, degit_1.default)(repo);
await emitter.clone(answer.name);
}
catch (err) {
console.log("\n");
(0, cli_ui_1.printError)("Project already exists or Folder is not empty", answer.name);
process.exit(1);
}
progressBar.update(50, {
doing: "Installing dependencies",
});
await packageManagerInstall({
packageManager: answer.packageManager,
directory: answer.name,
progressBar,
});
progressBar.update(90);
(0, change_package_info_1.changePackageName)({
directory: answer.name,
name: projectName,
});
progressBar.update(100);
progressBar.stop();
console.log("\n");
console.log("🐎 Project", chalk_1.default.green(answer.name), "created successfully!");
console.log("🤙 Run the following commands to start the project:\n");
console.log(chalk_1.default.bold.gray(`$ cd ${answer.name}`));
switch (answer.packageManager) {
case "npm":
console.log(chalk_1.default.bold.gray("$ npm run dev"));
break;
case "yarn":
console.log(chalk_1.default.bold.gray("$ yarn dev"));
break;
case "pnpm":
console.log(chalk_1.default.bold.gray("$ pnpm run dev"));
break;
case "bun":
console.log(chalk_1.default.bold.gray("$ bun dev"));
break;
}
console.log("\n");
console.log(chalk_1.default.bold.green((0, center_text_1.centerText)("Happy coding!")));
console.log(chalk_1.default.bold.gray((0, center_text_1.centerText)("Please consider donating to support the project.\n")));
console.log(chalk_1.default.bold.white((0, center_text_1.centerText)("💖 Sponsor: https://github.com/sponsors/expressots")));
console.log("\n");
}
};
exports.projectForm = projectForm;