km-web-plugin
Version:
ICE Web Plugin Initializer
335 lines (283 loc) • 9.48 kB
JavaScript
import { fileURLToPath } from "url";
import { dirname, join } from "path";
import inquirer from "inquirer";
import chalk from "chalk";
import ora from "ora";
import fs from "fs-extra";
import { execSync } from "child_process";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
console.log(
chalk.cyan(`
╔═══════════════════════════════════════╗
║ ICE Web Plugin Generator ║
║ Create Vue.js plugins for ║
║ Encompass ║
╚═══════════════════════════════════════╝
`),
);
function toPascalCase(str) {
return str
.replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => {
return index === 0 ? word.toUpperCase() : word.toUpperCase();
})
.replace(/\s+/g, "");
}
function toKebabCase(str) {
return str
.replace(/([a-z])([A-Z])/g, "$1-$2")
.replace(/[\s_]+/g, "-")
.toLowerCase();
}
function toCamelCase(str) {
const pascal = toPascalCase(str);
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
}
function sanitizeProjectName(name) {
return toKebabCase(name).replace(/[^a-z0-9-]/g, "");
}
async function promptUser() {
const answers = await inquirer.prompt([
{
type: "input",
name: "projectName",
message: "Project name:",
default: "my-web-plugin",
validate: (input) => {
if (!input.trim()) return "Project name is required";
if (!/^[a-zA-Z0-9-_\s]+$/.test(input)) {
return "Project name can only contain letters, numbers, spaces, hyphens, and underscores";
}
return true;
},
},
{
type: "input",
name: "appName",
message: "Application display name:",
default: "My Plugin",
validate: (input) => {
if (!input.trim()) return "Application name is required";
return true;
},
},
{
type: "confirm",
name: "includePopup",
message: "Include Popup support?",
default: false,
},
{
type: "confirm",
name: "includeGlobalTool",
message: "Include Global Tool support?",
default: false,
},
{
type: "confirm",
name: "installDeps",
message: "Install dependencies?",
default: true,
},
]);
answers.projectNameSanitized = sanitizeProjectName(answers.projectName);
answers.projectPath = join(process.cwd(), answers.projectNameSanitized);
answers.projectNameKebab = toKebabCase(answers.projectName);
answers.projectNameCamel = toCamelCase(answers.projectName);
answers.projectNamePascal = toPascalCase(answers.projectName);
answers.appNameNoSpaces = answers.appName.replace(/\s+/g, "");
answers.appNameKebab = toKebabCase(answers.appName);
answers.appNameCamel = toCamelCase(answers.appName);
answers.appNamePascal = toPascalCase(answers.appName);
answers.includePlugin = true;
answers.initGit = false;
answers.includeExampleForm = false;
return answers;
}
async function createProject(options) {
const spinner = ora("Creating project structure...").start();
try {
await fs.ensureDir(options.projectPath);
const baseTemplatePath = join(__dirname, "..", "templates", "base");
await copyTemplateFiles(baseTemplatePath, options.projectPath, options);
await fs.ensureDir(join(options.projectPath, "server"));
await fs.ensureDir(join(options.projectPath, "src", "stores"));
await fs.writeFile(
join(options.projectPath, "src", "lib", "formCode.js"),
"// Form code will go here\n",
);
if (options.includePopup) {
const popupTemplatePath = join(__dirname, "..", "templates", "popup");
await copyPluginSpecificFiles(
popupTemplatePath,
options.projectPath,
options,
"Popup",
);
}
if (options.includeGlobalTool) {
const globalToolTemplatePath = join(
__dirname,
"..",
"templates",
"globaltool",
);
await copyPluginSpecificFiles(
globalToolTemplatePath,
options.projectPath,
options,
"GlobalTool",
);
}
if (!options.includeExampleForm) {
await removeExampleFiles(options.projectPath);
}
spinner.succeed("Project structure created!");
// Initialize git (maybe used in future)
if (options.initGit) {
spinner.start("Initializing git repository...");
execSync("git init", { cwd: options.projectPath, stdio: "ignore" });
spinner.succeed("Git repository initialized!");
}
// Install dependencies
if (options.installDeps) {
spinner.start("Installing dependencies...");
execSync("npm install", { cwd: options.projectPath, stdio: "inherit" });
spinner.succeed("Dependencies installed!");
}
console.log(chalk.green("\n✨ Project created successfully!"));
console.log(chalk.cyan(`\nNext steps:`));
console.log(` ${chalk.gray("$")} cd ${options.projectNameSanitized}`);
if (!options.installDeps) {
console.log(` ${chalk.gray("$")} npm install`);
}
console.log(` ${chalk.gray("$")} npm run dev`);
console.log(chalk.cyan(`\nAvailable scripts:`));
console.log(
` ${chalk.gray("npm run dev")} - Start development server`,
);
console.log(
` ${chalk.gray("npm run dev-plugin")} - Start plugin development`,
);
console.log(` ${chalk.gray("npm run build-plugin")} - Build plugin`);
if (options.includePopup) {
console.log(
` ${chalk.gray("npm run dev-popup")} - Start popup development`,
);
console.log(` ${chalk.gray("npm run build-popup")} - Build popup`);
}
if (options.includeGlobalTool) {
console.log(
` ${chalk.gray("npm run dev-globaltool")} - Start global tool development`,
);
console.log(
` ${chalk.gray("npm run build-globaltool")} - Build global tool`,
);
}
} catch (error) {
spinner.fail("Failed to create project");
console.error(chalk.red(error.message));
process.exit(1);
}
}
async function copyTemplateFiles(src, dest, options, merge = false) {
const templateEngine = await import("./template-engine.js");
if (!(await fs.pathExists(src))) {
return;
}
const files = await fs.readdir(src);
for (const file of files) {
const srcPath = join(src, file);
const processedFilename = templateEngine.processFilename(file, options);
const destPath = join(dest, processedFilename);
const stat = await fs.stat(srcPath);
if (stat.isDirectory()) {
await fs.ensureDir(destPath);
await copyTemplateFiles(srcPath, destPath, options, merge);
} else {
const content = await fs.readFile(srcPath, "utf-8");
let processed = templateEngine.processTemplate(content, options);
processed = templateEngine.processSpecialFiles(
srcPath,
processed,
options,
);
if (!(await fs.pathExists(destPath)) || merge) {
await fs.writeFile(destPath, processed);
}
}
}
}
async function copyPluginSpecificFiles(src, dest, options, pluginType) {
const templateEngine = await import("./template-engine.js");
if (!(await fs.pathExists(src))) {
return;
}
const files = await fs.readdir(src);
for (const file of files) {
const srcPath = join(src, file);
const stat = await fs.stat(srcPath);
const processedFilename = templateEngine.processFilename(file, options);
let destPath;
if (file.includes("vite") && file.endsWith(".ts")) {
destPath = join(dest, processedFilename);
} else {
const libPath = join(dest, "src", "lib", pluginType);
await fs.ensureDir(libPath);
destPath = join(libPath, processedFilename);
}
if (stat.isDirectory()) {
await fs.ensureDir(destPath);
await copyTemplateFiles(srcPath, destPath, options, true);
} else {
const content = await fs.readFile(srcPath, "utf-8");
let processed = templateEngine.processTemplate(content, options);
processed = templateEngine.processSpecialFiles(
srcPath,
processed,
options,
);
await fs.writeFile(destPath, processed);
}
}
}
async function removeExampleFiles(projectPath) {
const filesToRemove = [
"src/components/ExampleForm.vue",
"src/composables/useExample.ts",
"src/services/ExampleService.ts",
"src/types/example.ts",
];
for (const file of filesToRemove) {
const filePath = join(projectPath, file);
if (await fs.pathExists(filePath)) {
await fs.remove(filePath);
}
}
}
async function main() {
try {
const options = await promptUser();
if (await fs.pathExists(options.projectPath)) {
const { overwrite } = await inquirer.prompt([
{
type: "confirm",
name: "overwrite",
message: `Directory ${options.projectNameSanitized} already exists. Overwrite?`,
default: false,
},
]);
if (!overwrite) {
console.log(chalk.yellow("Operation cancelled."));
process.exit(0);
}
await fs.remove(options.projectPath);
}
await createProject(options);
} catch (error) {
console.error(chalk.red("Error:"), error.message);
process.exit(1);
}
}
main();