@sarawebs/sb-bin
Version:
CLI tools for SaraWebs dev workflow
155 lines (132 loc) ⢠4.73 kB
JavaScript
import fs from "fs-extra";
import path from "path";
import { fileURLToPath } from "url";
import ejs from "ejs";
import { spawnSync } from "child_process";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
async function renderTemplate(srcFile, destFile, data) {
const template = await fs.readFile(srcFile, "utf-8");
const content = ejs.render(template, data);
await fs.outputFile(destFile, content);
console.log(`āļø Created ${destFile}`);
}
async function updatePackageJson(projectRoot, pkgTemplateFile, data) {
const template = await fs.readFile(pkgTemplateFile, "utf-8");
let pkgNew;
try {
pkgNew = JSON.parse(ejs.render(template, data));
} catch (err) {
console.error("ā Failed to parse package.json.ejs. Rendered content:");
console.error(ejs.render(template, data));
throw err;
}
const pkgPath = path.join(projectRoot, "package.json");
if (await fs.pathExists(pkgPath)) {
const pkgOld = JSON.parse(await fs.readFile(pkgPath, "utf-8"));
const merged = {
...pkgOld,
scripts: { ...pkgOld.scripts, ...pkgNew.scripts },
dependencies: { ...pkgOld.dependencies, ...pkgNew.dependencies },
devDependencies: { ...pkgOld.devDependencies, ...pkgNew.devDependencies },
};
await fs.writeFile(pkgPath, JSON.stringify(merged, null, 2));
console.log("š Updated package.json");
} else {
await fs.writeFile(pkgPath, JSON.stringify(pkgNew, null, 2));
console.log("š¦ Created package.json");
}
}
async function main() {
const argv = yargs(hideBin(process.argv)).option("init-config", {
type: "boolean",
description: "Copy default config.json to current working directory",
}).argv;
const serverName = argv._[0] || "server";
const projectRoot = path.resolve(process.cwd(), serverName);
const appId = String(Math.floor(Math.random() * 10000)).padStart(4, "0");
const templatesDir = path.join(__dirname, "../templates");
const cwdConfigPath = path.join(process.cwd(), "config.json");
const defaultConfigPath = path.join(templatesDir, "config.json");
// Handle --init-config
if (argv["init-config"]) {
if (await fs.pathExists(cwdConfigPath)) {
console.log("ā ļø config.json already exists in current directory");
} else {
await fs.copy(defaultConfigPath, cwdConfigPath);
console.log("ā
Default config.json copied to current directory");
}
process.exit(0);
}
console.log(`š¦ Creating project: ${serverName} (id: ${appId})`);
// Load config.json (cwd first, fallback to default)
let config;
if (await fs.pathExists(cwdConfigPath)) {
console.log("āļø Using config.json from current directory");
config = JSON.parse(await fs.readFile(cwdConfigPath, "utf-8"));
} else {
console.log("āļø Using default config.json from templates");
config = JSON.parse(await fs.readFile(defaultConfigPath, "utf-8"));
}
const context = { serverName, appId };
// Render files
for (const { src, dest } of config.files) {
await renderTemplate(
path.join(templatesDir, src),
path.join(projectRoot, dest),
context,
);
}
// Handle package.json separately
if (config.packageTemplate) {
await updatePackageJson(
projectRoot,
path.join(templatesDir, config.packageTemplate),
context,
);
}
console.log(`ā
Base project created in ${projectRoot}`);
// Run sb-crud-gen dynamically
if (config.runCrudGen && Array.isArray(config.crudList)) {
let command = "sb-crud-gen";
if (config.query === "prisma") {
await renderTemplate(
path.join(templatesDir, "src/utils/prisma.js.ejs"),
path.join(projectRoot, "src/utils/prisma.js"),
context,
);
command = "sb-crud-prisma";
}
for (const entity of config.crudList) {
const entityName = typeof entity === "string" ? entity : entity.name;
const entityFields = entity.fields || [];
console.log(`āļø Running ${command} for: ${entityName}...`);
const result = spawnSync(
"npx",
[command, "create", entityName, ...entityFields],
{
cwd: projectRoot,
stdio: "inherit",
shell: true,
},
);
if (result.error) {
console.error(`ā ${command} failed for ${entityName}:`, result.error);
process.exit(1);
}
}
}
console.log(`
ā” Navigate to '${serverName}' and run:
docker compose up --build
- Visit:
http://localhost:4000/api/health
`);
}
main().catch((err) => {
console.error("ā Error:", err);
process.exit(1);
});