import { Request, Response } from "express";
import { exec } from "child_process";
import path from "path";
import fs from "fs";
import archiver from "archiver";
const GENERATED_DIR = path.join(__dirname, "../../generated");
class GenerateAppController {
generateApp(req: Request, res: Response) {
const { appName, template, type, artifactPath } = req.body;
if (!appName) {
return res.status(400).json({ error: "appName is required" });
}
const appPath = path.join(GENERATED_DIR, appName);
// Remove if already exists
if (fs.existsSync(appPath)) {
fs.rmSync(appPath, { recursive: true, force: true });
}
exec(
`npx -p @angular/cli ng new ${appName} --skip-git --skip-install --defaults && cd ${appName} && npm install bootstrap`,
{ cwd: GENERATED_DIR },
(error, stdout, stderr) => {
if (error) {
return res.status(500).json({ error: stderr });
}
// Optionally inject Bootstrap layout if template === 'beautiful'
if (template === "beautiful") {
const indexHtmlPath = path.join(appPath, "src", "index.html");
const appComponentHtmlPath = path.join(appPath, "src", "app", "app.component.html");
const angularJsonPath = path.join(appPath, "angular.json");
// Add Bootstrap CDN to index.html
let indexHtml = fs.readFileSync(indexHtmlPath, "utf8");
indexHtml = indexHtml.replace(
"</head>",
` <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">\n</head>`
);
fs.writeFileSync(indexHtmlPath, indexHtml, "utf8");
// Add a beautiful Bootstrap layout to app.component.html
fs.writeFileSync(
appComponentHtmlPath,
`
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container-fluid">
<a class="navbar-brand" href="#">${appName}</a>
</div>
</nav>
<div class="container mt-5">
<div class="jumbotron bg-light p-5 rounded">
<h1 class="display-4">Welcome to ${appName}!</h1>
<p class="lead">This is a beautiful Angular + Bootstrap starter app generated by MCP Server.</p>
<hr class="my-4">
<p>Customize your app further to make it truly yours.</p>
</div>
</div>
`.trim(),
"utf8"
);
// Add Bootstrap CSS to angular.json styles
if (fs.existsSync(angularJsonPath)) {
const angularJson = JSON.parse(fs.readFileSync(angularJsonPath, "utf8"));
const stylesArr = angularJson.projects[appName].architect.build.options.styles;
if (!stylesArr.some((s: string) => s.includes("bootstrap"))) {
stylesArr.unshift("node_modules/bootstrap/dist/css/bootstrap.min.css");
fs.writeFileSync(angularJsonPath, JSON.stringify(angularJson, null, 2), "utf8");
}
}
}
// Create component directory if type is component
if (type === "component") {
const compDir = path.join(appPath, artifactPath);
if (!fs.existsSync(compDir)) {
fs.mkdirSync(compDir, { recursive: true });
}
}
// Zip the generated app
const zipPath = path.join(GENERATED_DIR, `${appName}.zip`);
const output = fs.createWriteStream(zipPath);
const archive = archiver("zip", { zlib: { level: 9 } });
output.on("close", () => {
res.download(zipPath, `${appName}.zip`, () => {
fs.unlinkSync(zipPath); // Clean up zip after download
});
});
archive.on("error", (err) => res.status(500).send({ error: err.message }));
archive.pipe(output);
archive.directory(appPath, false);
archive.finalize();
}
);
}
}
export default new GenerateAppController();