@megaorm/cli
Version:
This package allows you to communicate with MegaORM via commands directly from the command line interface (CLI).
185 lines • 8.31 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ModelHandler = exports.ModelHandlerError = void 0;
const promises_1 = __importDefault(require("fs/promises"));
const path_1 = require("path");
const text_1 = require("@megaorm/text");
const test_1 = require("@megaorm/test");
/**
* Custom error class for handling errors related to model operations.
*/
class ModelHandlerError extends Error {
}
exports.ModelHandlerError = ModelHandlerError;
/**
* Checks if the provided name matches the pattern for a valid model file.
*
* @param name The name to check if it matches a model file pattern.
* @returns `true` if the name matches the model file pattern (e.g., `User.js`), otherwise `false`.
*/
function isFile(name) {
// Pattern example: User.js
return /^[A-Z][a-z0-9]*([A-Z][a-z0-9]*)*\.(ts|js)$/.test(name);
}
/**
* Checks if the provided name matches the pattern for a valid TypeScript output file (either a map or declaration file).
*
* @param name - The name to check if it matches a TypeScript output file pattern.
* @returns `true` if the name matches the pattern for a `.js.map` or `.d.ts` file, otherwise `false`.
*/
function isMappedFile(name) {
// Pattern example: User.map.js or User.d.ts
return /^[A-Z][a-z0-9]*([A-Z][a-z0-9]*)*(\.js\.map|\.d\.ts)$/.test(name);
}
/**
* Safely deletes multiple model files at the specified paths.
*
* This function calls `unlink` for each path provided in the array, attempting to
* remove each corresponding file.
*
* @param paths An array of strings representing the absolute paths of the model files to be deleted.
* @returns A promise that resolves when all files are removed.
*/
function unlink(paths) {
return new Promise((resolve, reject) => {
return Promise.all(paths.map((path) => promises_1.default.unlink(path)))
.then(resolve)
.catch(reject);
});
}
/**
* Creates a model class name based on the table name.
*
* @param name The snake_case table name.
* @returns A model class name.
*/
function toClassName(name) {
return name
.split('_')
.map((part) => (0, text_1.toSingular)(part))
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join('');
}
/**
* This class provides functionality for adding, and removing model files
*
*/
class ModelHandler {
/**
* Instantiates a new ModelHandler.
*/
constructor() {
this.assets = (0, path_1.resolve)(__dirname, '../../assets');
}
/**
* Collects the absolute paths of model files from the specified directory.
*
* @param path The directory path from which to collect model files.
* @param map A boolean indicating whether to include mapped files (e.g., `.js.map`) in the resulting paths.
* @returns A promise that resolves with an array of absolute paths to valid model files.
* @throws `ModelHandlerError` if there is an issue collecting the files or if an invalid model file is encountered.
*/
collectPaths(path, map) {
return new Promise((resolve, reject) => {
promises_1.default.readdir(path)
.then((result) => {
// check if the directory is empty
if ((0, test_1.isEmptyArr)(result))
return resolve(result);
// filter map files
const files = map
? result
: result.filter((name) => !isMappedFile(name));
// validate each file
for (const file of files) {
if (!isFile(file)) {
if (map && isMappedFile(file))
continue;
return reject(new ModelHandlerError(`Invalid model file: ${String(file)}`));
}
}
return resolve(files.map((file) => (0, path_1.resolve)(path, file)));
})
.catch((e) => reject(new ModelHandlerError(e.message)));
});
}
/**
* Creates a new model file in the specified path.
*
* This method creates a new file for the specified table name in the given directory path.
* You can choose to create a TypeScript file or a JavaScript file based on the provided boolean flag.
*
* @param name The snake_case table name for which the model is created.
* @param path The directory path where the model file will be created.
* @param ts A boolean indicating whether to create a TypeScript file (`true`) or a JavaScript file (`false`).
* @returns A promise that resolves with a success message indicating the path of the created model file.
* @throws `ModelHandlerError` If the table name is invalid, the path is empty, or if any errors occur during file operations.
* @note The table name must be a snake_case table name, e.g., users, category_product.
*/
add(name, path, ts) {
return new Promise((resolve, reject) => {
if (!(0, test_1.isSnakeCase)(name)) {
return reject(new ModelHandlerError(`Invalid table name: ${String(name)}`));
}
if (!(0, test_1.isFullStr)(path)) {
return reject(new ModelHandlerError(`Invalid path: ${String(path)}`));
}
if (!(0, test_1.isBool)(ts))
ts = false;
const ext = ts ? 'ts' : 'js';
const layout = ts ? 'ts/model.txt' : 'js/model.txt';
const content = (0, path_1.resolve)(this.assets, layout);
promises_1.default.readFile(content, 'utf-8')
.then((template) => {
const className = toClassName(name);
const fileName = className.concat('.', ext);
const filePath = (0, path_1.resolve)(path, fileName);
const fileContent = template
.replace(/\[className\]/g, className)
.replace(/\[tableName\]/g, name)
.replace(/\[fileName\]/g, fileName);
// Create model file
promises_1.default.writeFile(filePath, fileContent, 'utf-8')
.then(() => resolve(`Model added in: ${filePath}`))
.catch((e) => reject(new ModelHandlerError(e.message)));
})
.catch((e) => reject(new ModelHandlerError(e.message)));
});
}
/**
* Removes the model associated with the given table name from the specified folder path.
*
* @param name The snake_case table name whose associated model is to be removed.
* @param path The directory path where the model files are located.
* @returns A promise that resolves with a success message indicating how many model files were removed.
* @throws `ModelHandlerError` If the table name is invalid, the path is empty.
* @note The table name must be a snake_case table name, e.g., users, category_product.
*/
remove(name, path) {
return new Promise((resolve, reject) => {
if (!(0, test_1.isSnakeCase)(name)) {
return reject(new ModelHandlerError(`Invalid table name: ${String(name)}`));
}
if (!(0, test_1.isFullStr)(path)) {
return reject(new ModelHandlerError(`Invalid path: ${String(path)}`));
}
return this.collectPaths(path, true)
.then((paths) => {
const unlinkPaths = paths.filter((path) => new RegExp(`${toClassName(name)}\.(ts|js|js\.map|d\.ts)$`).test(path));
if (unlinkPaths.length === 0) {
return Promise.reject(new ModelHandlerError(`No model found for table: ${name}`));
}
const message = `${unlinkPaths.length} model file${unlinkPaths.length === 1 ? '' : 's'} removed in:\n${unlinkPaths.join('\n')}`;
return unlink(unlinkPaths)
.then(() => resolve(message))
.catch(reject);
})
.catch(reject);
});
}
}
exports.ModelHandler = ModelHandler;
//# sourceMappingURL=ModelHandler.js.map