@godspeedsystems/godspeed
Version:
Godspeed CLI
527 lines • 24.7 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.genGraphqlSchema = exports.generateProjectFromDotGodspeed = exports.installPackage = exports.installDependencies = exports.compileAndCopyOrJustCopy = exports.generateFromExamples = exports.cloneProjectTemplate = exports.copyingLocalTemplate = exports.readDotGodspeed = exports.validateAndCreateProjectDirectory = void 0;
const path_1 = __importDefault(require("path"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const os_1 = __importDefault(require("os"));
const yaml_1 = __importDefault(require("yaml"));
const inquirer_1 = __importDefault(require("inquirer"));
const signale_1 = require("./signale");
const glob_1 = require("glob");
const ejs_1 = __importDefault(require("ejs"));
const simple_git_1 = __importDefault(require("simple-git"));
const chalk_1 = __importDefault(require("chalk"));
const child_process_1 = require("child_process");
const cross_spawn_1 = __importDefault(require("cross-spawn"));
const ora_1 = __importDefault(require("ora"));
const core_1 = require("@godspeedsystems/core");
const { exec } = require("child_process");
const userID = () => {
if (process.platform == "linux") {
const cmd = (0, child_process_1.spawnSync)("id", ["-u"]);
const uid = cmd.stdout?.toString().trim();
return uid;
}
else {
return "1000";
}
};
const validateAndCreateProjectDirectory = async (projectDirPath) => {
try {
const isDirExist = fs_extra_1.default.existsSync(projectDirPath);
if (isDirExist) {
const answers = await inquirer_1.default.prompt([
{
type: "confirm",
name: "overwrite",
message: () => {
console.log(`${chalk_1.default.yellow(projectDirPath)} already exists.\n`);
return chalk_1.default.red("Do you want to overwrite the project folder?");
},
default: false,
},
]);
if (!answers.overwrite) {
console.log(chalk_1.default.red("\nExiting godspeed create without creating project."));
process.exit(0);
}
fs_extra_1.default.rmSync(projectDirPath, { recursive: true, force: true });
}
fs_extra_1.default.mkdirSync(projectDirPath);
}
catch (error) {
console.log(chalk_1.default.red("Error while validateAndCreateProjectDirectory.", error));
}
};
exports.validateAndCreateProjectDirectory = validateAndCreateProjectDirectory;
const readDotGodspeed = async (projectDirPath) => {
let godspeedOptions = JSON.parse(fs_extra_1.default.readFileSync(path_1.default.resolve(projectDirPath, ".godspeed"), "utf-8"));
return godspeedOptions;
};
exports.readDotGodspeed = readDotGodspeed;
const copyingLocalTemplate = async (projectDirPath, templateDir) => {
if (!fs_extra_1.default.lstatSync(templateDir)) {
signale_1.log.fatal(`${chalk_1.default.red(templateDir)} does not exist or path is incorrect.`);
process.exit(1);
}
signale_1.log.wait(`Copying template from ${chalk_1.default.yellow(templateDir)}`);
fs_extra_1.default.cpSync(templateDir, projectDirPath, { recursive: true });
signale_1.log.success("Copying template successful.");
};
exports.copyingLocalTemplate = copyingLocalTemplate;
const cloneProjectTemplate = async (projectDirPath) => {
try {
signale_1.log.wait(`Cloning project template.`);
const git = (0, simple_git_1.default)();
const REPO = `${process.env.GITHUB_REPO_URL}`;
// clone godspeedsystems/godspeed-scaffolding repo
await git.clone(REPO, projectDirPath, {
"--branch": `${process.env.GITHUB_REPO_BRANCH}`,
"--depth": "1",
});
// TODO: remove git remote
signale_1.log.success("Cloning template successful.");
}
catch (error) {
signale_1.log.fatal(`Not able to reach template repository.`);
}
};
exports.cloneProjectTemplate = cloneProjectTemplate;
const generateFromExamples = async (projectDirPath, exampleName = "hello-world") => {
signale_1.log.wait(`Generating project with ${chalk_1.default.yellow(exampleName === "hello-world" ? "default" : exampleName)} examples.`);
if (!fs_extra_1.default.existsSync(path_1.default.resolve(projectDirPath, ".template", "examples", exampleName))) {
signale_1.log.fatal(`${chalk_1.default.red(exampleName)} is not a valid example.`);
process.exit(0);
}
fs_extra_1.default.cpSync(path_1.default.resolve(projectDirPath, ".template", "examples", exampleName), path_1.default.resolve(projectDirPath), {
recursive: true,
});
// read if there is an .godspeed file
if (fs_extra_1.default.existsSync(path_1.default.resolve(projectDirPath, ".template", "examples", exampleName, ".godspeed"))) {
return await (0, exports.readDotGodspeed)(projectDirPath);
}
else {
return null;
}
};
exports.generateFromExamples = generateFromExamples;
const compileAndCopyOrJustCopy = async (projectDirPath, sourceFolder, destinationFolder, templateData) => {
try {
const fileList = (0, glob_1.globSync)(path_1.default.resolve(projectDirPath, sourceFolder + "/**/*"));
let isUpdateCall = false;
try {
isUpdateCall = fs_extra_1.default
.lstatSync(path_1.default.resolve(process.cwd(), ".godspeed"))
.isFile();
}
catch (error) { }
fileList.map(async (sourceFilePath) => {
if (fs_extra_1.default.lstatSync(sourceFilePath).isFile()) {
// CREATE = used from outside
// UPDATE = used from the directory itself
let relativeDestinationPath;
relativeDestinationPath = !isUpdateCall
? path_1.default.relative(path_1.default.resolve(projectDirPath, sourceFolder), sourceFilePath)
: path_1.default.resolve(projectDirPath, destinationFolder, path_1.default.relative(path_1.default.resolve(projectDirPath, sourceFolder), sourceFilePath));
let finalDestinationWithFileName = path_1.default.resolve(projectDirPath, destinationFolder, relativeDestinationPath);
const finallFolderName = path_1.default.dirname(finalDestinationWithFileName);
const finalFileName = finalDestinationWithFileName.substring(finalDestinationWithFileName.lastIndexOf(path_1.default.sep) + 1);
const fileName = sourceFilePath.split(path_1.default.sep).pop() || "";
const isTemplate = fileName.endsWith(".ejs");
if (isTemplate) {
// compile and save
const template = await ejs_1.default.compile(fs_extra_1.default.readFileSync(sourceFilePath, "utf-8"));
const finalRender = await template(templateData);
if (!fs_extra_1.default.existsSync(finallFolderName)) {
await fs_extra_1.default.mkdirSync(finallFolderName, {
recursive: true,
});
}
if (finallFolderName && finalFileName) {
await fs_extra_1.default.writeFileSync(path_1.default.resolve(finallFolderName, fileName.replace(".ejs", "")), finalRender.replace(/^\s*\n/gm, ""));
}
}
else {
// just copy
if (fileName) {
try {
await fs_extra_1.default.cpSync(sourceFilePath, path_1.default.resolve(finallFolderName, finalFileName), {
recursive: true,
});
}
catch (error) {
throw error;
}
}
}
}
});
}
catch (error) {
throw error;
}
};
exports.compileAndCopyOrJustCopy = compileAndCopyOrJustCopy;
const installDependencies = async (projectDirPath, projectName) => {
const spinner = (0, ora_1.default)({
spinner: {
frames: ["🌍 ", "🌎 ", "🌏 ", "🌐 ", "🌑 ", "🌒 ", "🌓 ", "🌔 "],
interval: 180,
},
}).start("checking package managers...");
try {
// Check if pnpm is already available
const hasPnpm = await checkCommandExists("pnpm");
// If pnpm is not available, try to use corepack
if (!hasPnpm) {
const hasCorepack = await checkCommandExists("corepack");
if (hasCorepack) {
spinner.text = "setting up pnpm via corepack...";
await enableCorepackAndPnpm();
}
else {
spinner.text = "falling back to npm (slower)...";
}
}
// Choose the best available package manager
const packageManager = (await checkCommandExists("pnpm")) ? "pnpm" : "npm";
spinner.text = `installing dependencies with ${packageManager}...`;
spinner.stop();
const installArgs = packageManager === "pnpm"
? ["install"]
: ["install", "--prefer-offline"];
await new Promise((resolve, reject) => {
const child = (0, cross_spawn_1.default)(packageManager, installArgs, {
cwd: projectDirPath,
stdio: "inherit", // Changed from "pipe" to "inherit" to show output
});
child.on("close", (code) => {
if (code === 0) {
resolve();
}
else {
reject(new Error(`Process exited with code ${code}`));
}
});
child.on("error", (err) => {
reject(err);
});
});
console.log(`${chalk_1.default.green("\nSuccessfully created the project")} ${chalk_1.default.yellow(projectName)}.`);
console.log(`${chalk_1.default.green("Use `godspeed help` command for available commands.")} ${chalk_1.default.green.bold("\n\nHappy building microservices with Godspeed! 🚀🎉\n")}`);
}
catch (error) {
console.error("Error during installation:", error.message);
}
};
exports.installDependencies = installDependencies;
// Check if a command exists and is executable
async function checkCommandExists(command) {
try {
// Use 'which' on Unix-like systems or 'where' on Windows
const checkCmd = process.platform === "win32" ? "where" : "which";
return new Promise((resolve) => {
exec(`${checkCmd} ${command}`, (error) => {
resolve(!error);
});
});
}
catch (error) {
return false;
}
}
// Enable corepack and prepare pnpm
async function enableCorepackAndPnpm() {
try {
// Enable corepack
await new Promise((resolve, reject) => {
const child = (0, cross_spawn_1.default)("corepack", ["enable"], { stdio: "pipe" });
child.on("close", (code) => {
if (code === 0 || code === 1) {
resolve();
}
else {
reject(new Error(`Corepack enable failed with code ${code}`));
}
});
child.on("error", (err) => {
reject(err);
});
});
// Prepare and activate pnpm
await new Promise((resolve, reject) => {
const child = (0, cross_spawn_1.default)("corepack", ["prepare", "pnpm@latest", "--activate"], { stdio: "pipe" });
child.on("close", (code) => {
if (code === 0) {
resolve();
}
else {
reject(new Error(`pnpm preparation failed with code ${code}`));
}
});
child.on("error", (err) => {
reject(err);
});
});
}
catch (error) {
// Continue even if enabling corepack or preparing pnpm fails
console.log("Failed to set up pnpm, falling back to npm");
}
}
const installPackage = async (projectDirPath, package_name) => {
async function installprisma() {
const command = `npm install ${package_name}`;
return new Promise((resolve, reject) => {
const child = exec(command, {
cwd: projectDirPath,
stdio: "inherit", // Redirect output
});
child.on("exit", (code) => {
if (code === 0) {
resolve();
}
else {
reject(new Error(`Command exited with non-zero status code: ${code}`));
}
});
child.on("error", (error) => {
reject(error);
});
});
}
await installprisma();
};
exports.installPackage = installPackage;
const generateProjectFromDotGodspeed = async (projectName, projectDirPath, godspeedOptions, exampleName, isUpdate) => {
try {
signale_1.log.wait("Generating project files.");
const { gsNodeServiceVersion, servicePort, mongodb, postgresql, mysql, kafka, redis, elasticsearch, } = godspeedOptions;
// fetch UID information
let userUID = userID();
// generate .godspeed file
await fs_extra_1.default.writeFileSync(path_1.default.resolve(projectDirPath, ".godspeed"), JSON.stringify(godspeedOptions, null, 2));
// generate all the dot config files
if (!isUpdate) {
await fs_extra_1.default.cpSync(path_1.default.resolve(projectDirPath, ".template", "dot-configs"), path_1.default.resolve(projectDirPath), { recursive: true });
// generate package.json, tsConfig.json
for (let file of ["package.json", "tsconfig.json"]) {
const packageJson = await fs_extra_1.default.readJson(path_1.default.resolve(projectDirPath, `.template/${file}`));
await fs_extra_1.default.writeJsonSync(path_1.default.resolve(projectDirPath, file), {
...packageJson,
name: projectName,
}, {
spaces: "\t",
});
}
// generate .swcrc file
const swcrc = await fs_extra_1.default.readJson(path_1.default.resolve(projectDirPath, ".template/dot-configs/.swcrc"));
await fs_extra_1.default.writeJsonSync(path_1.default.resolve(projectDirPath, ".swcrc"), {
...swcrc,
}, {
spaces: "\t",
});
// create folder structure
if (exampleName) {
fs_extra_1.default.cpSync(path_1.default.resolve(projectDirPath, ".template", "examples", exampleName), path_1.default.resolve(projectDirPath), {
recursive: true,
});
}
else {
fs_extra_1.default.cpSync(path_1.default.resolve(projectDirPath, ".template", "defaults"), path_1.default.resolve(projectDirPath), {
recursive: true,
});
}
// create project folder structure
// const projectStructure = [
// "config",
// "src/events",
// "src/functions",
// "src/datasources",
// "src/mappings",
// ];
// projectStructure.map(async (folderName) => {
// await fsExtras.mkdirSync(path.resolve(projectDirPath, folderName), {
// recursive: true,
// });
// });
}
// TODO: generate helm-chats
// fsExtras.cpSync(
// path.resolve(projectDirPath, ".template/helm-charts"),
// path.resolve(projectDirPath),
// { recursive: true }
// );
/**
* Let's write
* <><><><><><>
* What are the files for the project
* .godspeed [Done]
* .vscode/* [Done]
* .devcontainer/* [Done]
* /helm-charts
* package.json [Done]
* /config [Done]
* /dot-config-files => .dockerignore, .env .eslintrc.json .gitignore .prettierrc .prismagenerator [Done]
* /src [Done]
* /datasources [Done]
* /events [Done]
* /functions [Done]
* /mappings [Done]
*/
await (0, exports.compileAndCopyOrJustCopy)(projectDirPath, ".template/.devcontainer", ".devcontainer", {
dockerRegistry: process.env.DOCKER_REGISTRY,
dockerPackageName: process.env.DOCKER_PACKAGE_NAME,
tag: gsNodeServiceVersion,
projectName: projectName,
servicePort,
userUID,
mongodb,
postgresql,
mysql,
kafka,
redis,
elasticsearch,
});
signale_1.log.success("Successfully generated godspeed project files.\n");
}
catch (error) {
signale_1.log.fatal("Error while generating files.", error);
}
};
exports.generateProjectFromDotGodspeed = generateProjectFromDotGodspeed;
const genGraphqlSchema = async () => {
try {
const availableApoloeventsources = (0, glob_1.globSync)(path_1.default.join(process.cwd(), "src/eventsources/*.yaml").replace(/\\/g, "/"));
// Filter files that contain 'Apollo' in their name
const apolloEventsources = availableApoloeventsources.map((file) => path_1.default.parse(file).name);
const questions = [
{
type: "checkbox",
name: "selectedOptions",
message: "Please select the Graphql Event Sources for which you wish to generate the Graphql schema from Godspeed event defs:",
choices: apolloEventsources,
},
];
async function runPrompt() {
try {
const answers = await inquirer_1.default.prompt(questions);
if (answers.selectedOptions.length == 0) {
console.log(chalk_1.default.red("Please select atleast one GraphQL eventsource"));
}
else {
await createSwaggerFile(answers.selectedOptions);
}
}
catch (error) {
console.error(error);
}
}
runPrompt();
}
catch (error) {
console.log(error);
}
const createSwaggerFile = async (eventSources) => {
const eventPath = path_1.default.join(process.cwd(), "/src/events");
const definitionsPath = path_1.default.join(process.cwd(), "/src/definitions");
const allEventsSchema = await (0, core_1.yamlLoader)(eventPath, true); //all events of the project
const definitions = await (0, core_1.yamlLoader)(definitionsPath, false);
eventSources.map(async (eventSourceName) => {
core_1.logger.info("Generating graphql schema for %s. First we will create swagger schema in %s", eventSourceName, os_1.default.tmpdir());
// Find out the events for this eventSourceName key
const eventSchemas = Object.fromEntries(Object.entries(allEventsSchema).filter(([key]) => {
const eventSourceKey = key.split(".")[0];
// eventSourceKey is the name of eventsources in this event definition.
// It could be one like 'http' or more like 'http & graphql'
if (eventSourceKey == eventSourceName) {
return true;
}
const eventSources = eventSourceKey.split("&").map((s) => s.trim());
return eventSources.includes(eventSourceName);
}));
if (Object.keys(eventSchemas).length === 0) {
core_1.logger.fatal(chalk_1.default.red(`Did not find any events for the ${eventSourceName} eventsource. Why don't you define the first one in the events folder?`));
process.exit(1);
}
// let swaggerSchema = await generateSwaggerui(eventSchemas,definitions);
let eventSourceConfig = yaml_1.default.parse(fs_extra_1.default.readFileSync(process.cwd() + `/src/eventsources/${eventSourceName}.yaml`, { encoding: "utf-8" }));
//The yaml file of the eventsource
let swaggerSchema = (0, core_1.generateSwaggerJSON)(eventSchemas, definitions, eventSourceConfig);
// For swagger-to-graphql plugin we need to save this file somewhere
const swaggerFilePath = path_1.default.join(os_1.default.tmpdir(), eventSourceName + "-swagger.json");
// Write the swagger.json file in the temp folder
await fs_extra_1.default.writeFileSync(swaggerFilePath, JSON.stringify(swaggerSchema, null, 2));
core_1.logger.info("Generated and saved swagger schema at temporary location %s. Now generating graphql schema from the same.", swaggerFilePath);
// genereate graphql schema
await generateGraphqlSchema(eventSourceName, swaggerFilePath);
});
};
};
exports.genGraphqlSchema = genGraphqlSchema;
async function generateGraphqlSchema(eventSourceName, swaggerFilePath) {
const command = `npx swagger-to-graphql --swagger-schema=${swaggerFilePath} > ./src/eventsources/${eventSourceName}.graphql`;
exec(command, (error, stdout, stderr) => {
if (error) {
console.log(chalk_1.default.red.bold(`Failed to generate Graphql schema for eventsource ${eventSourceName}`));
console.log(chalk_1.default.red(error.message));
return;
}
if (stderr) {
console.error(`stderr: ${stderr}`);
return;
}
console.log(chalk_1.default.green(`Graphql schema generated successfuly for eventsource ${eventSourceName} at ./src/eventsources/${eventSourceName}.graphql`));
});
}
const generateSwaggerui = async (eventsSchema, definitions) => {
let finalSpec = {};
const swaggerCommonPart = {
openapi: "3.0.0",
info: {
version: "0.0.1",
title: "Godspeed: Sample Microservice",
description: "Sample API calls demonstrating the functionality of Godspeed framework",
termsOfService: "http://swagger.io/terms/",
contact: {
name: "Mindgrep Technologies Pvt Ltd",
email: "talktous@mindgrep.com",
url: "https://docs.mindgrep.com/docs/microservices/intro",
},
license: {
name: "Apache 2.0",
url: "https://www.apache.org/licenses/LICENSE-2.0.html",
},
},
paths: {},
};
let swaggerSpecBase = JSON.parse(JSON.stringify(swaggerCommonPart));
finalSpec = swaggerSpecBase;
Object.keys(eventsSchema).forEach((event) => {
let apiEndPoint = event.split(".")[2];
apiEndPoint = apiEndPoint.replace(/:([^\/]+)/g, "{$1}"); //We take :path_param. OAS3 takes {path_param}
const method = event.split(".")[1];
const eventSchema = eventsSchema[event];
//Initialize the schema for this method, for given event
let methodSpec = {
summary: eventSchema.summary,
description: eventSchema.description,
requestBody: eventSchema.body || eventSchema.data?.schema?.body,
parameters: eventSchema.parameters ||
eventSchema.params ||
eventSchema.data?.schema?.params,
responses: eventSchema.responses,
};
// Set it in the overall schema
finalSpec.paths[apiEndPoint] = {
...finalSpec.paths[apiEndPoint],
[method]: methodSpec,
};
});
finalSpec.definitions = definitions;
return finalSpec;
};
//# sourceMappingURL=index.js.map