UNPKG

@udraft/core

Version:

uDraft is a language and stack agnostic code-generation tool that simplifies full-stack development by converting a single YAML file into code for rapid development.

681 lines 31.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.UDraft = void 0; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const lodash_1 = __importDefault(require("lodash")); const process_1 = require("process"); const module_1 = require("./module"); const renderer_required_error_1 = require("../errors/renderer-required-error"); const terminal_kit_1 = require("terminal-kit"); const udraft_error_1 = require("../errors/udraft-error"); const yaml_1 = require("yaml"); const model_1 = require("./model"); const feature_1 = require("./feature"); const attribute_1 = require("./attribute"); const attributes_1 = require("../shortcuts/attributes"); const field_1 = require("./field"); const parsing_error_1 = require("../errors/parsing-error"); const queries_1 = require("../shortcuts/queries"); class UDraft { constructor() { this._modules = []; this._attributes = []; this._renderers = []; } $modules() { return [...this._modules]; } $workingDir() { var _a; return (_a = this._workingDir) !== null && _a !== void 0 ? _a : ""; } $renderers() { return this._renderers; } $renderer(name) { return this.$renderers().find((r) => r.$name() == name) || null; } $requireRenderer(fromRendererClass, name) { const renderer = this.$renderer(name); if (renderer) return renderer; throw new renderer_required_error_1.RendererRequiredError(fromRendererClass.$name(), name); } $attributes() { return [...this._attributes]; } attributes(attributes) { this.removeAttributes(attributes); this._attributes = this._attributes.concat(attributes); return this; } removeAttributes(attributes) { this._attributes = this._attributes.filter((attribute) => !attributes.some((a) => a.$name() == attribute.$name())); return this; } static load(filePath) { const ext = path.extname(filePath); const content = fs.readFileSync(filePath, "utf-8"); switch (ext) { case ".yaml": return UDraft.yaml(content); case ".json": return UDraft.json(content); default: throw new udraft_error_1.UDraftError(`Unsupported file extension: ${ext}`); } } static yaml(yamlDraft) { try { const rawDraft = (0, yaml_1.parseDocument)(yamlDraft).toJSON(); return UDraft._parse(rawDraft); } catch (e) { if (e instanceof parsing_error_1.ParsingError) terminal_kit_1.terminal.red(`[uDraft] Parsing Error: `).red.bold(e.message + "\n"); else if (["YAMLParseError", "YAMLWarning"].includes(e.name) || e instanceof ReferenceError) terminal_kit_1.terminal .red(`[uDraft] Error in YAML file: `) .red.bold(`${e.message}\n`); else throw e; return null; } } static json(jsonDraft) { try { const rawDraft = JSON.parse(jsonDraft); return UDraft._parse(rawDraft); } catch (e) { if (e instanceof parsing_error_1.ParsingError) terminal_kit_1.terminal.red(`[uDraft] Parsing Error: `).red.bold(e.message + "\n"); else if (e instanceof SyntaxError) terminal_kit_1.terminal.red(`[uDraft] Error in JSON file: `).red.bold(`${e.stack}\n`); else throw e; return null; } } static _parse(rawDraft) { if (!(rawDraft === null || rawDraft === void 0 ? void 0 : rawDraft.draft)) throw new parsing_error_1.ParsingError(`No draft found in the file`); const draft = new UDraft(); const simpleTypes = ["string", "number", "int", "float", "boolean", "date"]; const modelTriggers = {}; const models = {}; const addModelTrigger = (modelName, trigger) => { if (models[modelName]) { trigger(models[modelName]); return; } if (!modelTriggers[modelName]) modelTriggers[modelName] = []; modelTriggers[modelName].push(trigger); }; const emitModelUpdate = (modelName, model) => { if (modelTriggers[modelName]) modelTriggers[modelName].forEach((trigger) => trigger(model)); }; const parseCallSignature = (signature) => { const match = signature.trim().match(/^\$([^\(]+)(?:\(*([^\)]*)\))*$/); if (!match) return null; return { fn: match[1].trim(), args: (match[2] || "") .split(",") .map((arg) => arg.trim()) .filter((v) => !!v), }; }; const parseFieldSignature = (signature) => { const match = signature.trim().match(/^([^\(]+)\[([^\)]+)\]$/); if (!match) return null; return { name: match[1].trim(), type: match[2].trim() }; }; const parseFieldAttributeSignature = (signature) => { const match = signature.trim().match(/^([^\(]+)(?:\(*(.+)\))*$/); if (!match) return null; if (match[1].match(/[\(\)]/g)) return null; return { name: match[1].trim(), value: (match[2] || "").trim().replace(/\)$/, ""), }; }; const parseModelSignature = (signature) => { const match = signature.match(/[\~\+]([^\(]+)\(*([^\)]*)\)*/); if (!match) return null; return { name: match[1].trim(), extends: (match[2] || "") .split(",") .map((arg) => arg.trim()) .filter((arg) => !!arg), }; }; const parseAttribute = (attributeKey, rawAttribute) => { const match = attributeKey.match(/\/([^\(]+)\(*([^\)]*)\)*/); if (!match) return null; const attributeName = match[1].trim(); const extendsAttributes = (match[2] || "") .split(",") .map((arg) => arg.trim()) .filter((arg) => !!arg) .map((arg) => { const source = models[arg]; if (!source) throw new parsing_error_1.ParsingError(`Source model ${arg} not found when extending the ${attributeName} attribute`); const extended = source.$attribute(attributeName); if (!extended) throw new parsing_error_1.ParsingError(`Attribute ${attributeName} not found in source model ${arg} when extending the ${attributeName} attribute`); return extended; }); let finalRawAttribute = typeof rawAttribute == "object" ? {} : rawAttribute; const mergeAttributeValue = (value) => { return lodash_1.default.mergeWith(finalRawAttribute, value, (objValue, srcValue) => { if (lodash_1.default.isArray(objValue)) { return objValue.concat(srcValue); } }); }; if (extendsAttributes.length > 0) { extendsAttributes.forEach((extended) => { const extendedValue = extended.$value(); if (typeof extendedValue == "object" && typeof rawAttribute == "object") { mergeAttributeValue(extendedValue); } else finalRawAttribute = extendedValue; }); if (typeof rawAttribute == "object") mergeAttributeValue(rawAttribute); return new attribute_1.UAttribute(attributeName, finalRawAttribute); } else return new attribute_1.UAttribute(attributeName, rawAttribute); }; const parseModel = (modelKey, rawModel) => { const isModel = modelKey[0] == "+"; const isEnum = modelKey[0] == "~"; if (!isModel && !isEnum) return null; const modelSignature = parseModelSignature(modelKey); if (!modelSignature) throw new parsing_error_1.ParsingError(`Invalid model declaration: ${modelKey}`); const modelName = modelSignature.name; const model = new model_1.UModel(modelName); if (isEnum) model.attributes([new attribute_1.UAttribute("enum", rawModel)]); else { const initialFieldNames = Object.keys(rawModel || {}) .map((fieldName) => { const fieldSignature = parseFieldSignature(fieldName); return fieldSignature === null || fieldSignature === void 0 ? void 0 : fieldSignature.name; }) .filter((name) => !!name); Object.keys(rawModel || {}).forEach((subModelKey) => { const subModelData = rawModel[subModelKey]; const attr = parseAttribute(subModelKey, subModelData); if (attr) return model.attributes([attr]); const call = parseCallSignature(subModelKey); if (call) { switch (call.fn) { case "pick": const srcModelName = call.args[0]; let didPick = false; addModelTrigger(srcModelName, (srcModel) => { if (!srcModel) throw new parsing_error_1.ParsingError(`Source model ${srcModelName} not found when pick fields to ${modelName} model: ${subModelKey}}`); const fieldsToPick = subModelData .map((fieldName) => { const renameField = fieldName.match(/([^>]+)\>([^>]*)/); if (renameField) return { from: renameField[1].trim(), to: renameField[2].trim(), }; return { from: fieldName, to: fieldName }; }) .filter((fieldToPick) => !initialFieldNames.includes(fieldToPick.to)); const pickField = ({ from, to, }) => { const srcField = srcModel.$field(from); if (!srcField) throw new parsing_error_1.ParsingError(`Field ${from} not found in source model ${srcModelName} when picking fields to ${modelName} model: ${subModelKey}}`); if (from === to) model.fields([srcField]); else model.fields([srcField.$clone(to)]); }; if (!didPick) { fieldsToPick.forEach(pickField); didPick = true; } else { // Refresh picked fields that were not removed fieldsToPick.forEach(({ from, to }) => { if (model.$field(to)) pickField({ from, to }); }); } }); break; case "remove": addModelTrigger(modelName, (updatedModel) => { if (updatedModel) updatedModel.remove(subModelData.filter((fieldToRemove) => !initialFieldNames.includes(fieldToRemove))); }); break; default: throw new parsing_error_1.ParsingError(`Invalid call inside Model ${modelName}: ${subModelKey}`); } return; } const signature = parseFieldSignature(subModelKey); if (!signature) throw new parsing_error_1.ParsingError(`Invalid field signature inside Model ${modelName} : ${subModelKey}`); let refModelName = ""; if (!simpleTypes.includes(signature.type)) { refModelName = signature.type; if (signature.type[0] == "&") { refModelName = signature.type.slice(1); signature.type = "reference"; } else signature.type = "nested"; } const field = new field_1.UField(signature.name, signature.type); (subModelData || []).forEach((attrKey) => { const attrSignature = parseFieldAttributeSignature(attrKey); if (!attrSignature) throw new parsing_error_1.ParsingError(`Invalid field attribute inside field ${subModelKey} from ${modelName} model: ${attrKey}`); if (attrSignature.value) attrSignature.value = eval(attrSignature.value); else attrSignature.value = null; field.attributes([ new attribute_1.UAttribute(attrSignature.name, attrSignature.value), ]); }); if (refModelName) { addModelTrigger(refModelName, (refModel) => { if (!refModel) throw new parsing_error_1.ParsingError(`Model ${refModelName} not found to reference inside Model ${modelName}: ${subModelKey}`); field.attributes([new attribute_1.UAttribute("ref", refModel)]); }); } model.fields([field]); }); modelSignature.extends.forEach((baseModelName) => { let didExtend = false; addModelTrigger(baseModelName, (baseModel) => { if (!baseModel) throw new parsing_error_1.ParsingError(`Base model ${baseModelName} not found when extending the ${modelName} model: ${modelKey}}`); if (!didExtend) { model.fields(baseModel .$fields() .filter((f) => !initialFieldNames.includes(f.$name()))); didExtend = true; } else { // Refresh extended fields that were not removed model.fields(baseModel.$fields().filter((field) => baseModel.$field(field))); } emitModelUpdate(modelName, model); }); }); } models[modelName] = model; emitModelUpdate(modelName, model); return model; }; const parseModule = (moduleKey, rawModule) => { const mod = new module_1.UModule(moduleKey); Object.keys(rawModule || {}).forEach((subModKey) => { const subModData = rawModule[subModKey]; const attr = parseAttribute(subModKey, subModData); if (attr) return mod.attributes([attr]); const model = parseModel(subModKey, subModData); if (model) return mod.models([model]); const feature = new feature_1.UFeature(subModKey); mod.features([feature]); Object.keys(subModData || {}).forEach((subFeatKey) => { const subFeatData = subModData[subFeatKey]; const featAttr = parseAttribute(subFeatKey, subFeatData); if (featAttr) return feature.attributes([featAttr]); if (subFeatKey == "input") { let didSetInput = false; Object.keys(subFeatData || {}).forEach((subInputKey) => { if (didSetInput) return; const subInputData = subFeatData[subInputKey]; const inputModel = parseModel(subInputKey, subInputData); if (inputModel) { feature.input(inputModel); didSetInput = true; } }); if (!didSetInput) addModelTrigger(subFeatData, (inputModel) => { if (!inputModel) throw new parsing_error_1.ParsingError(`Model ${subFeatData} not found when setting input for ${feature.$name()} feature`); feature.input(inputModel); }); } if (subFeatKey == "output") { let didSetOutput = false; Object.keys(subFeatData || {}).forEach((subOutputKey) => { if (didSetOutput) return; const subOutputData = subFeatData[subOutputKey]; const outputModel = parseModel(subOutputKey, subOutputData); if (outputModel) { feature.output(outputModel); didSetOutput = true; } }); if (!didSetOutput) addModelTrigger(subFeatData, (outputModel) => { if (!outputModel) throw new parsing_error_1.ParsingError(`Model ${subFeatData} not found when setting output for ${feature.$name()} feature`); feature.output(outputModel); }); } }); }); return mod; }; Object.keys(rawDraft.draft || {}).forEach((rootKey) => { const rootData = rawDraft.draft[rootKey]; const rootAttr = parseAttribute(rootKey, rootData); if (rootAttr) return draft.attributes([rootAttr]); const module = parseModule(rootKey, rootData); if (module) return draft.modules([module]); }); Object.keys(modelTriggers || {}).forEach((modelName) => { if (models[modelName]) return; modelTriggers[modelName].forEach((trigger) => { trigger(null); }); }); return draft; } extends(seed) { return this.modules(seed.$modules()); } modules(modules) { this.remove(modules); this._modules = this._modules.concat(modules); return this; } remove(modules) { this._modules = this._modules.filter((module) => !modules.some((m) => m.$name() == module.$name())); return this; } _goTo(workingDir) { terminal_kit_1.terminal.blue(`[uDraft] Working Directory: `).bold.magenta(`${workingDir}\n`); this._workingDir = workingDir; return this; } _clear() { terminal_kit_1.terminal.blue(`[uDraft] Clear Renderers\n`); this._renderers.forEach((renderer) => { renderer.clear(); }); this._renderers = []; return this; } begin(workingDir) { return this._pipeline([]).goTo(workingDir); } _pipeline(renderers, _controls) { if (!_controls) { _controls = { start: () => { }, error: (err) => { }, waitFor: Promise.resolve(), executionError: Promise.resolve(), }; _controls.waitFor = new Promise((resolve, reject) => { _controls.start = resolve; }); _controls.executionError = new Promise((resolve, reject) => { _controls.error = reject; }); } const execution = _controls.waitFor .then(() => new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { try { for (const renderer of renderers) { yield this.render(renderer); } resolve(); } catch (err) { reject(err); } }))) .catch((err) => { if (!_controls) throw err; if (err instanceof udraft_error_1.UDraftError) { terminal_kit_1.terminal.red(`[uDraft] Pipeline Error: `).black.bold(err.message + "\n"); } else _controls.error(err); const halt = new Promise(() => { }); return halt; }); const cursor = { goTo: (workingDir) => { execution.then(() => this._goTo(workingDir)); return cursor; }, clear: () => { execution.then(() => this._clear()); return cursor; }, pipeline: (renderers) => { return this._pipeline(renderers, Object.assign(Object.assign({}, _controls), { waitFor: execution })); }, exec: () => { terminal_kit_1.terminal.blue(`[uDraft] uDraft: `).bold.green(`${(0, queries_1.$attr)(this, "name")}\n`); _controls.start(); return Promise.race([execution, _controls.executionError]).then(() => { terminal_kit_1.terminal.bold.green(`\n[uDraft] Done!\n\n`); }); }, }; return cursor; } render(renderer) { return __awaiter(this, void 0, void 0, function* () { terminal_kit_1.terminal.blue(`[uDraft] Rendering: `).bold.yellow(`${renderer.$name()}\n`); yield renderer.init(this); const paths = renderer.$paths(); const contents = []; for (const renderPath of paths) { renderPath.path = renderPath.path.startsWith("/") ? renderPath.path : path.join((0, process_1.cwd)(), this.$workingDir(), renderPath.path); const renderDir = path.dirname(renderPath.path); if (!fs.existsSync(renderDir)) fs.mkdirSync(renderDir, { recursive: true }); let content = ""; if (fs.existsSync(renderPath.path)) content = fs.readFileSync(renderPath.path, "utf-8"); contents.push({ key: renderPath.key, content, meta: renderPath.meta, }); } yield renderer.run(contents); const modules = renderer.$selection().modules || []; const models = renderer.$selection().models || []; const features = renderer.$selection().features || []; if (modules.length) { terminal_kit_1.terminal.white(`[uDraft] Selected Modules: `); terminal_kit_1.terminal.white.bold(modules.map((module) => module.$name()).join(", ") + "\n"); } if (models.length) { terminal_kit_1.terminal.white(`[uDraft] Selected Models: `); terminal_kit_1.terminal.white.bold(models.map((model) => model.$name()).join(", ") + "\n"); } if (features.length) { terminal_kit_1.terminal.white(`[uDraft] Selected Features: `); terminal_kit_1.terminal.white.bold(features.map((feature) => feature.$name()).join(", ") + "\n"); } for (const renderPath of paths) { const output = renderer.$output(renderPath.key); if (output === null) continue; const dir = path.dirname(renderPath.path); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } fs.writeFileSync(renderPath.path, output.content, "utf-8"); terminal_kit_1.terminal .white(`[uDraft] Output: `) .bold.white(`${renderPath.key} `) .black(`${renderer .$resolveRelativePath((0, process_1.cwd)() + "/index.js", renderPath.path) .replace(/^\.\//, "")}\n`); } this._renderers.push(renderer); return this; }); } $json() { const json = { attributes: [], models: {}, modules: {}, }; const attrToJson = (attr) => ({ name: attr.$name(), value: attr.$value(), }); const fieldToJson = (field) => { var _a; return ({ name: field.$name(), type: field.$type(), isArray: !!(0, queries_1.$attr)(field, (0, attributes_1._array)()), ref: (_a = (0, queries_1.$attr)(field, (0, attributes_1._ref)())) === null || _a === void 0 ? void 0 : _a.$name(), attributes: field .$attributes() .filter((attr) => !["ref", "array"].includes(attr.$name())) .map(attrToJson), }); }; const modelToJson = (model) => { var _a, _b; const enumData = (0, queries_1.$attr)(model, (0, attributes_1._enum)()); const jsonModel = { name: model.$name(), module: (_b = (_a = (0, queries_1.$attr)(model, (0, attributes_1._rootModule)())) === null || _a === void 0 ? void 0 : _a.$name()) !== null && _b !== void 0 ? _b : "", enum: enumData !== null && enumData !== void 0 ? enumData : undefined, fields: !enumData ? model.$fields().map(fieldToJson) : undefined, attributes: model .$attributes() .filter((attr) => !["enum", "rootModule"].includes(attr.$name())) .map(attrToJson), }; if (!json.models[model.$name()]) json.models[model.$name()] = jsonModel; return jsonModel; }; this.$modules().forEach((mod) => { const models = {}; const features = {}; mod.$models().forEach((model) => { if ((0, queries_1.$attr)(mod, (0, attributes_1._rootModule)()) != mod || !!models[model.$name()]) return; models[model.$name()] = modelToJson(model); }); mod.$features().forEach((feature) => { var _a, _b; const input = feature.$input(); const output = feature.$output(); features[feature.$name()] = { name: feature.$name(), attributes: feature.$attributes().map(attrToJson), module: (_b = (_a = (0, queries_1.$attr)(feature, (0, attributes_1._rootModule)())) === null || _a === void 0 ? void 0 : _a.$name()) !== null && _b !== void 0 ? _b : "", input: input ? modelToJson(input) : undefined, output: output ? modelToJson(output) : undefined, }; }); const jsonModule = { name: mod.$name(), attributes: mod.$attributes().map(attrToJson), features, models, }; json.modules[mod.$name()] = jsonModule; }); return json; } } exports.UDraft = UDraft; //# sourceMappingURL=draft.js.map