build-capibara
Version:
A CLI tool to create a Capibara project structure.
678 lines (567 loc) âĸ 24 kB
JavaScript
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const { exec } = require('child_process');
const readline = require('readline');
// Define the folder structure
const structure = {
'.capi': {
'additional': {
'cli': {
'create.js': `
const fs = require('fs');
const path = require('path');
const readline = require('readline');
const createutilitySystemForRoles = require("./utility");
// Function to create a directory if it doesn't exist
const createDirectory = (dirPath) => {
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
console.log(\`Directory created: \${dirPath}\`);
}
};
// Function to create a file with a basic template
const createFile = (filePath, template) => {
fs.writeFileSync(filePath, template);
console.log(\`File created: \${filePath}\`);
};
// Main function to handle the creation of controllers, middleware, and models
const createCapiControllerFile = (folderName, fileName) => {
const baseDir = path.join('./workspace', 'controller');
const dirPath = folderName ? path.join(baseDir, folderName) : baseDir;
createDirectory(dirPath);
const template = \`// Controller: \${fileName}
require('module-alias/register');
const capi = require("@capi");
const \${fileName}Controller = (req, res) => {
const { name } = req.body;
// Return a new Promise
return new Promise((resolve, reject) => {
try {
// Simulate some asynchronous operation if needed
setTimeout(() => {
// Resolve the promise with the response
resolve(res.status(201).json({ message: name }));
}, 0);
} catch (error) {
// Reject the promise with the error
reject(res.status(500).json({ error: error.message }));
}
});
};
module.exports = \${fileName}Controller;\`;
const filePath = path.join(dirPath, \`\${fileName}.js\`);
createFile(filePath, template);
};
const createCapiModelsFile = (folderName, fileName) => {
const baseDir = path.join('./workspace', 'models');
const dirPath = folderName ? path.join(baseDir, folderName) : baseDir;
createDirectory(dirPath);
const template = \`// Model: \${fileName}
const { capiMongo } = require("@capi");
const \${fileName}SchemaDefinition = {
name: capiMongo.String(true).def("Name"), //Field Name : name, Type : String(Required = true), Default("Name")
description: capiMongo.String().def("this is Description") //Field Name : description, Type : String(Required = false), Default("this is Description")
};
const \${fileName} = capiMongo.Model('\${fileName}', \${fileName}SchemaDefinition);
module.exports = \${fileName};\`;
const filePath = path.join(dirPath, \`\${fileName}.js\`);
createFile(filePath, template);
};
const createCapiMiddlewareFile = (folderName, fileName) => {
const baseDir = path.join('./workspace', 'middleware');
const dirPath = folderName ? path.join(baseDir, folderName) : baseDir;
createDirectory(dirPath);
const template = \`// Middleware: \${fileName}
const { capi } = require("@capi");
const middleware = (req, res, next) => {
next();
};
module.exports = middleware;\`;
const filePath = path.join(dirPath, \`\${fileName}.js\`);
createFile(filePath, template);
};
// Create readline interface
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
// Function to ask questions
const askQuestions = () => {
rl.question('Type? (controller/middleware/models/utility): ', (type) => {
if (!['controller', 'middleware', 'models', 'utility'].includes(type)) {
console.error('Invalid type. Use "controller", "middleware", or "models", or "utility".');
rl.close();
return;
}
if (type === "utility") {
createutilitySystemForRoles();
}
rl.question('Folder name? (Press Enter to skip): ', (folderName) => {
folderName = folderName || '';
rl.question('File names? (use comma to add more file): ', (fileNames) => {
if (!fileNames) {
console.error('File names cannot be empty.');
rl.close();
return;
}
const fileNameArray = fileNames.split(',').map(name => name.trim());
console.log('Creating Files...');
fileNameArray.forEach(fileName => {
if (type === "controller") {
createCapiControllerFile(folderName, fileName);
} else if (type === "models") {
createCapiModelsFile(folderName, fileName);
} else if (type === "middleware") {
createCapiMiddlewareFile(folderName, fileName);
}
});
console.log('Success!');
rl.close();
});
});
});
};
// Start asking questions
askQuestions();
`,
'utility.js': `
const fs = require('fs');
const path = require('path');
const readline = require('readline');
const createDirectory = (dirPath) => {
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
console.log(\`Directory created: \${dirPath}\`);
}
};
const createFile = (filePath, template) => {
fs.writeFileSync(filePath, template);
console.log(\`File created: \${filePath}\`);
};
const createutilitySystemForRoles = () => {
const baseDir = path.join('./workspace', 'controller');
const dirPath = path.join(baseDir, "CapiAuthentication");
createDirectory(dirPath);
const template = \`// Controller: AuthMiddleware
const test = 1;
\`;
const filePath = path.join(dirPath, \`AuthMiddleware.js\`);
createFile(filePath, template);
}
module.exports = createutilitySystemForRoles;
`,
},
'connection.js': `
const mongoose = require("mongoose");
require('module-alias/register')
const {db, corsOptions} = require("@capiConfig")
mongoose.connect(db.uri, { useNewUrlParser: true, useUnifiedTopology: true })
.then(() => {
console.log('MongoDB connected successfully');
})
.catch(err => {
console.error('MongoDB connection error:', err);
});
`,
'sanitize.js': `
const mongoose = require("mongoose");
require('module-alias/register')
const sanitizeBody = (req, model) => {
const allowedFields = Object.keys(model.schema.paths);
return Object.keys(req.body).every(field => allowedFields.includes(field));
};
const sanitizeTypes = (req, model) => {
const allowedFields = Object.keys(model.schema.paths);
return allowedFields.every(field => {
if (req.body[field] !== undefined) {
const expectedType = model.schema.paths[field].instance;
const actualType = typeof req.body[field];
return expectedType === 'String' && actualType === 'string' ||
expectedType === 'Number' && actualType === 'number' ||
expectedType === 'Boolean' && actualType === 'boolean' ||
expectedType === 'ObjectID' && mongoose.Types.ObjectId.isValid(req.body[field]) ||
expectedType === 'Array' && Array.isArray(req.body[field]);
}
return true;
});
};
const validateFields = (body, model) => {
// Assuming model has a schema with required fields
const requiredFields = Object.keys(model.schema.paths).filter(key => model.schema.paths[key].isRequired);
for (const field of requiredFields) {
if (!body[field] || body[field].trim() === '') {
return false; // Field is missing or empty
}
}
return true; // All required fields are valid
};
module.exports = {sanitizeBody, sanitizeTypes, validateFields}
`
},
'.capi.js': `
/* CAPIBARA FRAMEWORK BY ANDREW
MADE IN 2024 VER 1.0.6
STILL IN PROGRESS
NEXT LIST OF WORK
- CUSTOM FUNCTION FOR EVERY ENDPOINT
- ROLE BASE SYSTEM
- RESTRICT ENDPOINT BY URL
- JWT TOKEN GENERATOR FOR PUBLIC ETC
- PUT
- DEL
THE VERY VERY SOON
- FRONT END DEVELOPMENT KINDA LIKE NEXT JS USING REACT
- MYSQL SUPPORT
- NPX INSTALL
*/
require("dotenv").config();
require('module-alias/register')
require("@connection")
const express = require('express');
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const {sanitizeBody, sanitizeTypes} = require("@additional/sanitize");
const mongoose = require('mongoose');
const app = express();
const TOKEN = process.env.TOKEN;
app.use(express.json());
class capi {
constructor(app) {
this.app = app;
}
Get(model, endpoint, middlewareOrOptions, options = { findbyId: false, strict: false, pagination: false }) {
let middleware = null;
if (typeof middlewareOrOptions === 'function') {
middleware = middlewareOrOptions;
} else if (typeof middlewareOrOptions === 'object' && middlewareOrOptions !== null) {
options = middlewareOrOptions;
}
options = options || {};
this.app.get(endpoint, middleware || ((req, res, next) => next()), async (req, res) => {
try {
let result;
if (options.findbyId) {
const id = req.params.id;
if (!mongoose.Types.ObjectId.isValid(id)) {
return res.status(404).send("Invalid Object Id");
}
const validate = await model.findById(id);
if (!validate) {
return res.status(404).send("Invalid Object Id");
}
result = validate;
} else {
const queryData = req.query;
// Pagination logic
if (options.pagination) {
const limit = parseInt(req.query.limit) || 10; // Default limit
const page = parseInt(req.query.page) || 1; // Default page
const skip = (page - 1) * limit;
result = await model.find(queryData).limit(limit).skip(skip);
const totalCount = await model.countDocuments(queryData);
const totalPages = Math.ceil(totalCount / limit);
// Include pagination info in the response
res.json({
totalCount,
totalPages,
currentPage: page,
limit,
data: result
});
} else {
result = await model.find(queryData);
res.json(result);
}
}
} catch (error) {
res.status(500).json({ error: error.message });
}
});
}
Post(model, endpoint, middlewareOrOptions, options = { users: false, strict: false }, customfunc) {
let middleware = null;
if (typeof middlewareOrOptions === 'function') {
middleware = middlewareOrOptions;
} else if (typeof middlewareOrOptions === 'object' && middlewareOrOptions !== null) {
options = middlewareOrOptions;
}
options = options || {};
// Define the default request handler
const defaultHandler = async (req, res) => {
try {
const sanitizedData = req.body;
const isBodyValid = sanitizeBody(req, model);
const isTypesValid = sanitizeTypes(req, model);
if (options.users) {
if (sanitizedData.password) {
sanitizedData.password = bcrypt.hashSync(sanitizedData.password, 10);
}
if (sanitizedData.Password) {
sanitizedData.Password = bcrypt.hashSync(sanitizedData.Password, 10);
}
}
if (options.users && options.strict) {
if (req.body.email) {
const email = await model.findOne({ email: sanitizedData.email });
if (email) {
return res.status(400).json({ error: "Email already exists" });
}
}
if (req.body.username) {
const user = await model.findOne({ username: sanitizedData.username });
if (user) {
return res.status(400).json({ error: "Username already exists" });
}
}
}
if (!isTypesValid)
return res.status(400).json({ error: "Invalid input data!" });
if (!isBodyValid)
return res.status(400).json({ error: "Invalid field!" });
const newDocument = new model(sanitizedData);
await newDocument.save();
res.status(201).json({ message: "Document created successfully", data: newDocument });
} catch (error) {
res.status(500).json({ error: error.message });
}
};
this.app.post(endpoint, middleware || ((req, res, next) => next()), async (req, res) => {
if (customfunc) {
await customfunc(req, res);
} else {
await defaultHandler(req, res);
}
});
}
Login(model, endpoint, middleware = null) {
this.app.post(endpoint, middleware || ((req, res, next) => next()), async (req, res) => {
try {
const sanitizedData = req.body;
const isBodyValid = sanitizeBody(req, model);
const isTypesValid = sanitizeTypes(req, model);
if (!isTypesValid || !isBodyValid) {
return res.status(400).json({ error: "Invalid input data!" });
}
const username = sanitizedData.username;
const email = sanitizedData.email;
let user
// Find user by username or email
if(username){
user = await model.findOne({username: username});
}else if(email){
user = await model.findOne({email: email})
}
console.log(user)
if (!user) {
return res.status(401).json({ error: "Invalid credentials!" });
}
// Check if password is provided
const inputPassword = sanitizedData.password; // Use sanitizedData.Password
if (!inputPassword) {
return res.status(400).json({ error: "Password is required!" });
}
// Log the user password and input password for debugging
console.log("Input Password:", inputPassword);
console.log("User Password (hashed):", user.password || user.Password);
// Validate the password
const userPassword = user.password || user.Password; // Assuming user.Password is the alternative field
const isPasswordValid = bcrypt.compareSync(inputPassword, userPassword);
if (!isPasswordValid) {
return res.status(401).json({ error: "Invalid credentials!" });
}
// Generate a JWT token
const token = jwt.sign({ username: user.username }, TOKEN, { expiresIn: '1h' });
res.json({ message: "Login successful", token });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
}
generateToken(user) {
return "your_generated_token";
}
}
class capiDb {
constructor(mongoose) {
this.mongoose = mongoose;
}
/**
* Generates a Mongoose model.
* @param {string} modelName - The name of the model.
* @param {Object} schemaDefinition - The schema definition for the model.
* @returns {mongoose.Model} - The generated Mongoose model.
*/
Model(modelName, schemaDefinition) {
const schema = new this.mongoose.Schema(schemaDefinition);
return this.mongoose.model(modelName, schema);
}
// Create a new field definition object
createFieldDefinition(type, required = false) {
const fieldDefinition = {
type,
required,
default: undefined, // Initialize default as undefined
def: (value = "" && required === false) => {
fieldDefinition.default = value; // Set the default value in the field definition
return fieldDefinition; // Return the field definition for chaining
},
set: function(value) {
// If the value is an empty string, return the default value
return value === "" ? this.default : value;
}
};
return fieldDefinition;
}
String(required = false) {
const field = this.createFieldDefinition(String, required);
field.set = field.set.bind(field); // Bind the setter to the field
return field;
}
Number(required = false) {
return this.createFieldDefinition(Number, required);
}
Bool(required = false) {
return this.createFieldDefinition(Boolean, required);
}
Text(required = false) {
return this.createFieldDefinition(String, required); // Use String for text
}
ObjectId(required = false, ref = null) {
const definition = { type: this.mongoose.Schema.Types.ObjectId, required };
if (ref) definition.ref = ref;
return definition;
}
}
const PORT = process.env.PORT || 1337;
app.listen(PORT, () => {
console.log(\`Server is running on port \${PORT}\`);
});
const capibara = new capi(app);
const capiMongo = new capiDb(mongoose)
module.exports = {capibara, capiMongo};
`,
},
'node_modules': {}, // Placeholder for node_modules
'workspace': {
'controller': {},
'middleware': {},
'models': {},
'utils': {},
'app.js': `require('module-alias/register')
const {capibara} = require("@capi")`,
},
'.env': `
MONGODB_URI=mongodb+srv://alluserdev347:aj6YJft4WMRPKGVw@sulutinfo.tmsbe.mongodb.net/capibaratest?retryWrites=true&w=majority&appName=sulutinfo
TOKEN=JWkCapItC*\ID12233345@TKnVs
`,
'.gitignore': './node_modules',
'capi.config.js': `
require("dotenv").config();
const db = {
uri: process.env.MONGODB_URI
}
const corsOptions = {
origin:"",
credentials: false
}
module.exports = {db, corsOptions}
`,
'package-lock.json': '',
'package.json': JSON.stringify({
"name": "capibara",
"version": "1.0.0",
"main": "index.js",
"type": "commonjs",
"scripts": {
"dev": "nodemon ./workspace/app",
"create": "node .capi/additional/cli/create.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"bcrypt": "^5.1.1",
"body-parser": "^1.20.3",
"cors": "^2.8.5",
"dotenv": "^16.4.7",
"dotnet": "^1.1.4",
"express": "^4.21.2",
"jsonwebtoken": "^9.0.2",
"module-alias": "^2.2.3",
"mongoose": "^8.10.0",
"nodemon": "^3.1.9"
},
"_moduleAliases": {
"@capiConfig": "./capi.config.js",
"@capi": "./.capi/.capi",
"@connection": "./.capi/additional/connection",
"@additional": "./.capi/additional",
"models": "./workspace/models",
"controller": "./workspace/controller",
"middleware": "./workspace/middleware",
"utils": "./workspace/utils"
}
}, null, 4),
'Capi.md': '# Project Title\n\nBasic Script For Setup :\n- npm run create\nBasic running Script :\n- npm run dev',
};
// Function to create the folder structure
const createStructure = (basePath, structure) => {
for (const key in structure) {
const newPath = path.join(basePath, key);
if (typeof structure[key] === 'object') {
fs.mkdirSync(newPath, { recursive: true });
createStructure(newPath, structure[key]);
} else {
fs.writeFileSync(newPath, structure[key]);
}
}
};
// Read user inputs using readline
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('Are you sure you want to create this App? (Yes/No): ', (answer) => {
if (answer.toLowerCase() === 'yes' || answer.toLowerCase() === 'y') {
rl.question('Application Name: ', (dirName) => {
fs.mkdirSync(dirName, { recursive: true });
createStructure(path.join(process.cwd(), dirName), structure);
// Create a basic .env file
fs.writeFileSync(path.join(dirName, '.env'), '');
// Start loading animation
// Start loading animation with more interesting messages
const loadingMessages = [
'đ Going to Capibara Planet',
'đ Almost There.',
'đĻĢ Waking the capibara..',
'đ§ Setting Things Up...',
'âī¸ Just a Few More Seconds....'
];
let loadIndex = 0;
const loadingInterval = setInterval(() => {
process.stdout.clearLine();
process.stdout.cursorTo(0);
process.stdout.write(loadingMessages[loadIndex]);
loadIndex = (loadIndex + 1) % loadingMessages.length;
}, 1000); // Increased time for better visibility
// Initialize npm and install packages
exec(`cd ${dirName} && npm i && code .`, (error, stdout, stderr) => {
clearInterval(loadingInterval); // Stop loading animation
process.stdout.clearLine();
process.stdout.cursorTo(0);
if (error) {
console.error(`exec error: ${error}`);
return;
}
console.log(`\nInstallation Output:\n${stdout}`);
console.log(`Success! Project Created. Use "cd ${dirName}" and "npm run dev" to start the app.`);
rl.close();
});
});
} else {
console.log('Project creation aborted.');
rl.close();
}
});