wakeb-starter-cli
Version:
A powerful CLI tool for generating CRUD modules, common modules, and components with Vue 3 form schemas, featuring intelligent field detection and automatic schema generation
161 lines (134 loc) ⢠4.52 kB
JavaScript
import fs from "fs";
import path from "path";
import inquirer from "inquirer";
import readline from "readline";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const COMPONENTS_TEMPLATE_DIR = path.resolve(
__dirname,
"../../templates/components"
);
const PROJECT_COMPONENTS_DIR = path.resolve("src/components/common");
export default async function addComponent() {
const componentTree = buildComponentTree(COMPONENTS_TEMPLATE_DIR);
const flatChoices = flattenComponentTree(componentTree);
const { selectedItems } = await inquirer.prompt([
{
type: "checkbox",
name: "selectedItems",
message: "š§© Select components or folders:",
choices: flatChoices,
pageSize: 20,
},
]);
const filesToCopy = [];
for (const item of selectedItems) {
if (item.type === "folder") {
const folderChoices = buildFolderChoices(item.children);
const { selectedChildren } = await inquirer.prompt([
{
type: "checkbox",
name: "selectedChildren",
message: `š Select items inside ${item.name}:`,
choices: folderChoices,
pageSize: 20,
},
]);
if (selectedChildren.includes("BACK")) continue;
const chosenChildren = selectedChildren.includes("SELECT_ALL")
? item.children
: selectedChildren;
filesToCopy.push(...chosenChildren);
} else {
filesToCopy.push(item);
}
}
for (const file of filesToCopy) {
const templatePath = file.relativePath;
const targetPath = path.join(PROJECT_COMPONENTS_DIR, templatePath);
await copyComponentWithConfirm(templatePath, targetPath);
}
console.log("\nš All selected items have been processed.\n");
}
function buildComponentTree(dir, base = "") {
const items = fs.readdirSync(dir);
const tree = [];
items.forEach((item) => {
const itemPath = path.join(dir, item);
const stats = fs.statSync(itemPath);
if (stats.isDirectory()) {
tree.push({
name: item,
type: "folder",
relativePath: path.join(base, item),
children: buildComponentTree(itemPath, path.join(base, item)),
});
} else if (item.endsWith(".vue")) {
tree.push({
name: item.replace(".vue", ""),
type: "file",
relativePath: path.join(base, item),
});
}
});
return tree;
}
function flattenComponentTree(tree) {
return tree.map((item) => ({
name: item.type === "folder" ? `š ${item.name}` : `š§© ${item.name}`,
value: item,
short: item.name,
checked:
item.type === "folder"
? checkFolderFullyExists(item)
: checkIfExists(item.relativePath),
}));
}
function buildFolderChoices(children) {
return [
{ name: "ā
Select All", value: "SELECT_ALL" },
{ name: "ā© Back", value: "BACK" },
...children.map((child) => ({
name: `š§© ${child.name}`,
value: child,
checked: checkIfExists(child.relativePath),
})),
];
}
function checkFolderFullyExists(folder) {
if (!folder.children || folder.children.length === 0) return false;
return folder.children.every((child) => checkIfExists(child.relativePath));
}
function checkIfExists(relativePath) {
const targetPath = path.join(PROJECT_COMPONENTS_DIR, relativePath);
return fs.existsSync(targetPath);
}
async function copyComponentWithConfirm(templateRelativePath, targetPath) {
const sourcePath = path.join(COMPONENTS_TEMPLATE_DIR, templateRelativePath);
const targetDir = path.dirname(targetPath);
if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true });
if (fs.existsSync(targetPath)) {
const overwrite = await askYesNo(
`ā ļø ${templateRelativePath} already exists. Overwrite? (y/N): `
);
if (!overwrite) {
console.log(`ā© Skipped: ${templateRelativePath}`);
return;
}
}
fs.copyFileSync(sourcePath, targetPath);
console.log(`ā
${templateRelativePath} ā ${targetPath}`);
}
function askYesNo(question) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
return new Promise((resolve) => {
rl.question(question, (answer) => {
rl.close();
resolve(answer.trim().toLowerCase() === "y");
});
});
}