UNPKG

ahmedeltatawe

Version:

Project Initializer

360 lines (308 loc) 10.4 kB
#!/usr/bin/env node import inquirer from "inquirer"; import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; import { execSync } from "child_process"; import chalk from "chalk"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const CHOICES = fs.readdirSync(path.join(__dirname, "templates")); const QUESTIONS = [ { name: "project-choice", type: "list", message: "What project template would you like to generate?", choices: CHOICES, }, { name: "project-name", type: "input", message: "Project name:", validate: function (input) { if (/^([A-Za-z\-\_\d])+$/.test(input)) return true; else return "Project name may only include letters, numbers, underscores and hashes."; }, }, ]; const CURR_DIR = process.cwd(); let nodeFlag = false; // Set nodeFlag as a global variable with a default value of false // Command line arguments const [, , condition, folderPath, nodeFlagParam] = process.argv; const [, , ...args] = process.argv; const options = { condition: null, folderPathOrNameOrLink: null, nodeFlagParam: "", }; // Parse command line arguments for (let i = 0; i < args.length; i++) { const arg = args[i]; switch (arg) { case "-a": case "-r": case "-c": options.condition = arg; break; case "-h": options.condition = arg; options.folderPathOrNameOrLink = ""; break; case "-y": // Check if the previous option was '-a' if (options.condition === "-a") { options.nodeFlagParam = arg; } else { console.error(`Invalid usage of '-y' flag.`); process.exit(1); } break; default: if (arg.startsWith("-")) { console.error(`Invalid option: ${arg}`); process.exit(1); } else { options.folderPathOrNameOrLink = arg; } break; } } if (!args.length) { options.condition = ""; options.folderPathOrNameOrLink = ""; } // Check for unsupported or invalid options if (Object.values(options).some((value) => value === null)) { console.error(`Missing required option or invalid usage.`); process.exit(1); } // Update the value of nodeFlag based on the command line argument if (nodeFlagParam === "-y") { nodeFlag = true; } function createDirectoryContents(templatePath, newProjectPath) { const filesToCreate = fs.readdirSync(templatePath); filesToCreate.forEach((file) => { const origFilePath = path.join(templatePath, file); // get stats about the current file const stats = fs.statSync(origFilePath); if (stats.isFile()) { const contents = fs.readFileSync(origFilePath, "utf8"); // Rename if (file === ".npmignore") file = ".gitignore"; if (condition === "-a") { fs.writeFileSync( path.join(__dirname, "templates", newProjectPath, file), contents, "utf8" ); } else { fs.writeFileSync( path.join(CURR_DIR, newProjectPath, file), contents, "utf8" ); } } else if (stats.isDirectory()) { if (condition === "-a") { if (file === "node_modules" && !nodeFlag) { return; } else { fs.mkdirSync(path.join(__dirname, "templates", newProjectPath, file)); } } else { fs.mkdirSync(path.join(CURR_DIR, newProjectPath, file)); } // Then recursively copy contents createDirectoryContents( path.join(templatePath, file), path.join(newProjectPath, file) ); } }); } if (!condition) { inquirer.prompt(QUESTIONS).then((answers) => { const projectChoice = answers["project-choice"]; const projectName = answers["project-name"]; const templatePath = path.join(__dirname, "templates", projectChoice); const projectPath = path.join(CURR_DIR, projectName); // Create project directory if it doesn't exist if (!fs.existsSync(projectPath)) { fs.mkdirSync(projectPath); } else { console.log(`\nProject directory '${projectName}' already exists.`); { process.exit(1); } } createDirectoryContents(templatePath, projectName); console.log("\nTemplate successfully created."); }); } else if (condition === "-a") { // Validate folder path if (!folderPath) { console.error("\nError: provide a folder path."); { process.exit(1); } } // Validate folder existence if (!fs.existsSync(folderPath)) { console.error("\nError: Folder does not exist."); { process.exit(1); } } // Create project name from folder path const projectName = path.basename(folderPath); // Create directory for new project if it doesn't exist const templateFolderPath = path.join(__dirname, "templates", projectName); if (!fs.existsSync(templateFolderPath)) { fs.mkdirSync(templateFolderPath); } else { console.log(`\nFolder '${templateFolderPath}' already exists.`); { process.exit(1); } } // Copy contents from folderPath to projectName/template createDirectoryContents(folderPath, projectName); // InformSync user about successful operation if (nodeFlag) { console.log("\nTemplate successfully created with node_modules."); } else { console.log("\nTemplate successfully created."); } } else if (condition === "-r") { const templateToRemove = process.argv[3]; // Validate if the template exists const templatePathToRemove = path.join( __dirname, "templates", templateToRemove ); if (!fs.existsSync(templatePathToRemove)) { console.error(`\nError: Template '${templateToRemove}' does not exist.`); { process.exit(1); } } // Remove the template directory fs.rmSync(templatePathToRemove, { recursive: true }); // InformSync user about successful operation console.log(`\nTemplate '${templateToRemove}' was successfully removed.`); } else if (condition === "-c") { const runCommand = (command) => { return new Promise((resolve, reject) => { try { execSync(command, { stdio: "inherit" }); resolve(); // Resolve the promise when the command completes successfully } catch (error) { console.error(`Failed to Execute ${command}`, error); reject(error); // Reject the promise with the error if the command fails } }); }; const urlRegex = /^(https?|ftp):\/\/(([a-z\d]([a-z\d-]*[a-z\d])?\.)+[a-z]{2,}|localhost)(\/[-a-z\d%_.~+]*)*(\?[;&a-z\d%_.~+=-]*)?(\#[-a-z\d_]*)?$/i; const repoLink = process.argv[3]; if (!urlRegex.test(repoLink)) { console.log("Invalid repository link provided."); process.exit(1); } else { console.log("Repository link is valid."); } const gitCommand = `git clone --depth 1 ${repoLink}`; let projectName; // Check if a changed name is provided in the clone command if (repoLink.includes(" ")) { // Extract the project name from the clone command const cloneCommandParts = repoLink.split(" "); // Split the clone command by whitespace projectName = cloneCommandParts[cloneCommandParts.length - 1]; // Get the last part as the project name } else { // Extract the project name from the repository link projectName = path.basename(repoLink, ".git"); } runCommand(gitCommand) .then(async () => { console.log("\nRepository Cloned"); // Remove the .git folder const clonedFolderPath = path.join(CURR_DIR, projectName); const gitFolderPath = path.join(clonedFolderPath, ".git"); if (fs.existsSync(gitFolderPath)) { await fs.promises.rm(gitFolderPath, { recursive: true }); } // Run the 'init -a' command with the cloned folder path const initCommand = `init -a "${clonedFolderPath}"`; await runCommand(initCommand); // Remove the cloned project folder asynchronously try { await fs.promises.rm(clonedFolderPath, { recursive: true }); } catch (error) { console.error(`Failed to remove cloned folder: ${error}`); } }) .catch((error) => { console.error("Error cloning repository:", error); process.exit(1); // Exit the script with an error status code }); } else if (condition === "-h") { // Clear the terminal by printing ANSI escape codes process.stdout.write("\u001b[2J\u001b[0;0H"); const message = ` ${chalk.bold.underline.white("Package Commands:")} ${chalk.green("Create a New Template:")} - Type ${chalk.cyan("'init'")} and press Enter at your desired location. ${chalk.green("Add a Template:")} - Use the ${chalk.cyan( "-a" )} flag followed by the path in quotes. By default it skips the node_modules folder. ${chalk.yellow("Example:")} ${chalk.cyan("init -a")} ${chalk.yellow( '"C:\\Users\\{User}\\Desktop\\Projects\\Ongoing Projects"' )} - But if you want to add node_modules folder with the template you are creating then ${chalk.cyan( "-y" )} flag after the command. ${chalk.yellow("Example:")} ${chalk.cyan("init -a")} ${chalk.yellow( '"C:\\Users\\{User}\\Desktop\\Projects\\Ongoing Projects"' )} ${chalk.cyan("-y")} ${chalk.green("Clone a Repository and Add as a Template:")} - Use the ${chalk.cyan( "-c" )} flag followed by the repository link in quotes. ${chalk.yellow("Example:")} ${chalk.cyan("init -c")} ${chalk.yellow( '"https://github.com/user/repoitoryName"' )} ${chalk.green("Remove a Template:")} - Use the ${chalk.cyan( "-r" )} flag followed by the exact name of the template in quotes. ${chalk.yellow("Example:")} ${chalk.cyan("init -r")} ${chalk.yellow( '"Template Name"' )} ${chalk.green("Help:")} - Use ${chalk.cyan("init -h")} to see help. Made By Sooraj Gupta Email : soorajgupta00@gmail.com Github Repository : https://github.com/s54a/s54a-init `; console.log(message); } else if (condition) { console.log( chalk.red( ` Invalid command: "${condition}". Please use one of the supported commands. Run ${chalk.yellow("init -h")} to see help. ` ) ); } else { console.log( chalk.red( `An error occurred or an invalid command was provided. Run ${chalk.yellow("init -h")} to see help.` ) ); }