UNPKG

erdia

Version:

CLI to generate mermaid.js ER diagram using TypeORM entity

1,325 lines (1,250 loc) 102 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var __publicField = (obj, key, value) => { __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); return value; }; var __accessCheck = (obj, member, msg) => { if (!member.has(obj)) throw TypeError("Cannot " + msg); }; var __privateGet = (obj, member, getter) => { __accessCheck(obj, member, "read from private field"); return getter ? getter.call(obj) : member.get(obj); }; var __privateAdd = (obj, member, value) => { if (member.has(obj)) throw TypeError("Cannot add the same private member more than once"); member instanceof WeakSet ? member.add(obj) : member.set(obj, value); }; var __privateSet = (obj, member, value, setter) => { __accessCheck(obj, member, "write to private field"); setter ? setter.call(obj, value) : member.set(obj, value); return value; }; // src/index.ts var src_exports = {}; __export(src_exports, { CE_CHANGE_KIND: () => CE_CHANGE_KIND, CE_COLUMN_ATTRIBUTE: () => CE_COLUMN_ATTRIBUTE, CE_COMMAND_LIST: () => CE_COMMAND_LIST, CE_DEFAULT_VALUE: () => CE_DEFAULT_VALUE, CE_ENTITY_VERSION_FROM: () => CE_ENTITY_VERSION_FROM, CE_IMAGE_FORMAT: () => CE_IMAGE_FORMAT, CE_MERMAID_THEME: () => CE_MERMAID_THEME, CE_OUTPUT_COMPONENT: () => CE_OUTPUT_COMPONENT, CE_OUTPUT_FORMAT: () => CE_OUTPUT_FORMAT, CE_PROJECT_NAME_FROM: () => CE_PROJECT_NAME_FROM, CE_RECORD_KIND: () => CE_RECORD_KIND, CE_TEMPLATE_NAME: () => CE_TEMPLATE_NAME, Logger: () => Logger, SymbolDataSource: () => SymbolDataSource, SymbolDefaultTemplate: () => SymbolDefaultTemplate, SymbolLogger: () => SymbolLogger, SymbolTemplate: () => SymbolTemplate, SymbolTemplateRenderer: () => SymbolTemplateRenderer, TemplateRenderer: () => TemplateRenderer, applyPrettier: () => applyPrettier, betterMkdir: () => betterMkdir, buildDocumentCommandHandler: () => buildDocumentCommandHandler, buildOptionBuilder: () => buildOptionBuilder, building: () => building, cleanDocumentCommandHandler: () => cleanDocumentCommandHandler, cleaning: () => cleaning, commonOptionBuilder: () => commonOptionBuilder, compareDatabase: () => compareDatabase, configTemplate: () => configTemplate, container: () => container, createHtml: () => createHtml, createImageHtml: () => createImageHtml, createLogger: () => createLogger, createMarkdown: () => createMarkdown, createPdfHtml: () => createPdfHtml, dedupeManyToManyRelationRecord: () => dedupeManyToManyRelationRecord, defaultExclude: () => defaultExclude, documentOptionBuilder: () => documentOptionBuilder, ejecting: () => ejecting, flushDatabase: () => flushDatabase, getAutoCompleteSource: () => getAutoCompleteSource, getColumnAttributeKey: () => getColumnAttributeKey, getColumnHash: () => getColumnHash, getColumnRecord: () => getColumnRecord, getColumnType: () => getColumnType, getColumnWeight: () => getColumnWeight, getComment: () => getComment, getConfigContent: () => getConfigContent, getConfigFilePath: () => getConfigFilePath, getCwd: () => getCwd, getDataSource: () => getDataSource, getDatabaseName: () => getDatabaseName, getEntityHash: () => getEntityHash, getEntityName: () => getEntityName, getEntityPropertyName: () => getEntityPropertyName, getEntityRecord: () => getEntityRecord, getEntityRecords: () => getEntityRecords, getFileVersion: () => getFileVersion, getFindFile: () => getFindFile, getGlobFiles: () => getGlobFiles, getIndexHash: () => getIndexHash, getIndexRecord: () => getIndexRecord, getIndexRecords: () => getIndexRecords, getInverseRelationMetadata: () => getInverseRelationMetadata, getIsNullable: () => getIsNullable, getJoinColumn: () => getJoinColumn, getManyToManyEntityMetadata: () => getManyToManyEntityMetadata, getManyToManyJoinColumn: () => getManyToManyJoinColumn, getManyToOneJoinColumn: () => getManyToOneJoinColumn, getMetadata: () => getMetadata, getOutputDirPath: () => getOutputDirPath, getPackageName: () => getPackageName, getPlainRelationType: () => getPlainRelationType, getProjectName: () => getProjectName, getPuppeteerConfig: () => getPuppeteerConfig, getRelationHash: () => getRelationHash, getRelationRecord: () => getRelationRecord, getRelationRecords: () => getRelationRecords, getRenderData: () => getRenderData, getSlashEndRoutePath: () => getSlashEndRoutePath, getTemplate: () => getTemplate, getTemplateDirPath: () => getTemplateDirPath, getTemplateModulePath: () => getTemplateModulePath, getTemplatePath: () => getTemplatePath, getTemplates: () => getTemplates, getVersion: () => getVersion, initConfigCommandHandler: () => initConfigCommandHandler, initializing: () => initializing, loadDataSource: () => loadDataSource, loadTemplates: () => loadTemplates, openDatabase: () => openDatabase, outputOptionBuilder: () => outputOptionBuilder, preLoadConfig: () => preLoadConfig, processDatabase: () => processDatabase, templateEjectCommandHandler: () => templateEjectCommandHandler, writeToImage: () => writeToImage, writeToPdf: () => writeToPdf }); module.exports = __toCommonJS(src_exports); // src/common/getColumnHash.ts function getColumnHash(column) { const baseHash = [column.entity, column.dbName].join(":"); const base64 = Buffer.from(baseHash).toString("base64"); return base64; } // src/common/getDatabaseName.ts var import_util = require("util"); function getDatabaseName(options) { const name = options.database; if (typeof name === "string") { return name; } if (name instanceof Uint8Array) { return new import_util.TextDecoder().decode(name); } return "default"; } // src/common/getEntityHash.ts function getEntityHash(entity) { const baseHash = [entity.entity, entity.dbName].join(":"); const base64 = Buffer.from(baseHash).toString("base64"); return base64; } // src/common/getFileVersion.ts var import_jsonc_parser = require("jsonc-parser"); var import_semver = __toESM(require("semver")); function getFileVersion(buf) { const rawJson = buf.toString(); if (import_semver.default.valid(rawJson)) { return rawJson; } const parsed = (0, import_jsonc_parser.parse)(rawJson); const { version } = parsed; if (version == null || version === "") { throw new Error(`invalid version file: ${rawJson}`); } return version; } // src/common/getIndexHash.ts function getIndexHash(column) { const baseHash = [column.entity, column.dbName].join(":"); const base64 = Buffer.from(baseHash).toString("base64"); return base64; } // src/common/getPackageName.ts function getPackageName(json) { const { name } = json; if (typeof name === "string" && name !== "") { return name; } throw new Error("Cannot get project name from package.json"); } // src/configs/const-enum/CE_PROJECT_NAME_FROM.ts var CE_PROJECT_NAME_FROM = { DATABASE: "db", APPLICATION: "app" }; // src/common/getProjectName.ts async function getProjectName(dataSource, json, option) { if (option.projectName === CE_PROJECT_NAME_FROM.DATABASE) { if (dataSource.options.database != null) { const databaseName = getDatabaseName(dataSource.options); return databaseName; } const name2 = getPackageName(json); return name2; } const name = getPackageName(json); return name; } // src/configs/const-enum/CE_DEFAULT_VALUE.ts var CE_DEFAULT_VALUE = { CONFIG_FILE_NAME: ".erdiarc", TSCONFIG_FILE_NAME: "tsconfig.json", HTML_INDEX_FILENAME: "index.html", HTML_MERMAID_FILENAME: "mermaid.html", MARKDOWN_FILENAME: "erdia.md", DATABASE_FILENAME: "erdiadb.json", VERSION_FILENAME: ".erdiaverrc", TEMPLATES_PATH: "templates", DATA_SOURCE_FILE_FUZZY_SCORE_LIMIT: 50, OUTPUT_DIRECTORY_FUZZY_SCORE_LIMIT: 50 }; // src/configs/modules/getCwd.ts function getCwd(env) { if ((env.USE_INIT_CWD ?? "false") === "false") { return process.cwd(); } if (env.INIT_CWD != null) { return env.INIT_CWD; } return process.cwd(); } // src/modules/files/getFindFile.ts var import_find_up = __toESM(require("find-up")); async function getFindFile(filename, option) { const finded = await (0, import_find_up.default)(filename, option); return finded; } // src/modules/files/betterMkdir.ts var import_my_easy_fp = require("my-easy-fp"); var import_my_node_fp = require("my-node-fp"); var import_node_fs = __toESM(require("fs")); var import_pathe = __toESM(require("pathe")); async function betterMkdir(filePath) { const isFilePathExist = await (0, import_my_node_fp.exists)(filePath); if ((0, import_my_easy_fp.isFalse)(isFilePathExist)) { const extname = import_pathe.default.extname(filePath); const hasExtname = extname !== "" && extname.length > 0; if (hasExtname) { const dirPath = await (0, import_my_node_fp.getDirname)(filePath); await import_node_fs.default.promises.mkdir(dirPath, { recursive: true }); } else { await import_node_fs.default.promises.mkdir(filePath, { recursive: true }); } } } // src/modules/files/getOutputDirPath.ts var import_my_easy_fp2 = require("my-easy-fp"); var import_my_node_fp2 = require("my-node-fp"); var import_pathe2 = __toESM(require("pathe")); async function getOutputDirPath(option, cwd) { const outputDirPath = option.output ?? cwd; const resolvedOutputDirPath = import_pathe2.default.resolve(outputDirPath); if ((0, import_my_easy_fp2.isFalse)(await (0, import_my_node_fp2.exists)(resolvedOutputDirPath))) { await betterMkdir(import_pathe2.default.join(resolvedOutputDirPath)); return resolvedOutputDirPath; } if ((0, import_my_easy_fp2.isFalse)(await (0, import_my_node_fp2.isDirectory)(outputDirPath))) { return import_pathe2.default.resolve(await (0, import_my_node_fp2.getDirname)(outputDirPath)); } return import_pathe2.default.resolve(outputDirPath); } // src/common/getVersion.ts var import_dayjs = __toESM(require("dayjs")); var import_fs = __toESM(require("fs")); var import_pathe3 = __toESM(require("pathe")); async function getVersionFilename(option, versionFilename) { if (option.versionPath != null) { const filename2 = await getFindFile( import_pathe3.default.join(await getOutputDirPath({ output: option.versionPath }, getCwd(process.env)), versionFilename), { cwd: getCwd(process.env) } ); return filename2; } const filename = await getFindFile(versionFilename, { cwd: getCwd(process.env) }); return filename; } async function getVersion(json, option) { if (option.versionFrom === "package.json") { const { version } = json; if (!(typeof version === "string") || version == null) { throw new Error(`Cannot found version field in package.json`); } return { version }; } if (option.versionFrom === "file") { const getVersionFile = async () => { const filename = await getVersionFilename(option, CE_DEFAULT_VALUE.VERSION_FILENAME); if (filename != null) { return filename; } const fromConfig = await getVersionFilename(option, CE_DEFAULT_VALUE.CONFIG_FILE_NAME); return fromConfig; }; const versionFilename = await getVersionFile(); if (versionFilename == null) { throw new Error(`Cannot found version file: ${CE_DEFAULT_VALUE.VERSION_FILENAME}`); } const versionBuf = await import_fs.default.promises.readFile(versionFilename); const version = getFileVersion(versionBuf); return { version: version.trim() }; } return { version: `${(0, import_dayjs.default)().valueOf()}` }; } // src/modules/containers/container.ts var import_awilix = require("awilix"); var container = (0, import_awilix.createContainer)(); // src/modules/containers/keys/SymbolDataSource.ts var SymbolDataSource = Symbol("data-source"); // src/common/getMetadata.ts var import_dayjs2 = __toESM(require("dayjs")); var import_filenamify = __toESM(require("filenamify")); var import_read_pkg = __toESM(require("read-pkg")); async function getMetadata(option) { const dataSource = container.resolve(SymbolDataSource); const json = await (0, import_read_pkg.default)({ normalize: false }); const rawName = await getProjectName(dataSource, json, option); const name = (0, import_filenamify.default)(rawName, { replacement: "_" }); const { version } = await getVersion(json, option); return { name, title: option.title, version, createdAt: (0, import_dayjs2.default)().format(), updatedAt: (0, import_dayjs2.default)().format() }; } // src/common/getPlainRelationType.ts function getPlainRelationType(relationType) { if (relationType === "one-to-one") { return "one-to-one"; } if (relationType === "many-to-many") { return "many-to-many"; } return "one-to-many"; } // src/common/getRelationHash.ts function getRelationHash(relation) { const entities = [relation.entity, relation.inverseEntityName].sort((l, r) => l.localeCompare(r)); const plainRelationType = getPlainRelationType(relation.relationType); const baseHash = [...entities, plainRelationType].join(":"); const base64 = Buffer.from(baseHash).toString("base64"); return base64; } // src/creators/applyPretter.ts var import_consola = __toESM(require("consola")); var import_my_easy_fp3 = require("my-easy-fp"); async function applyPrettier(document, format, configPath) { try { const prettier = (await import("prettier")).default; const prettierConfig = await prettier.resolveConfig(configPath ?? "."); const formatted = await prettier.format(document, { ...prettierConfig ?? {}, parser: format === "md" ? "markdown" : format }); return formatted; } catch (caught) { const err = (0, import_my_easy_fp3.isError)(caught, new Error("unknown error raised from prettier appling function")); import_consola.default.error(err.message); import_consola.default.error(err.stack); return document; } } // src/configs/const-enum/CE_OUTPUT_COMPONENT.ts var CE_OUTPUT_COMPONENT = { TABLE: "table", ER: "er" }; // src/modules/containers/keys/SymbolTemplateRenderer.ts var SymbolTemplateRenderer = Symbol("template-renderer"); // src/templates/cosnt-enum/CE_TEMPLATE_NAME.ts var CE_TEMPLATE_NAME = { HTML_DOCUMENT_TOC: "html-document-toc", HTML_DOCUMENT: "html-document", HTML_MERMAID: "html-mermaid", HTML_MERMAID_TOC: "html-mermaid-toc", HTML_MERMAID_DIAGRAM: "html-mermaid-diagram", HTML_STYLE: "html-style", HTML_TABLE: "html-table", IMAGE_DOCUMENT: "image-document", IMAGE_MERMAID_DIAGRAM: "image-mermaid-diagram", IMAGE_STYLE: "image-style", MARKDOWN_DOCUMENT: "markdown-document", MARKDOWN_MERMAID_DIAGRAM: "markdown-mermaid-diagram", MARKDOWN_TABLE: "markdown-table", MARKDOWN_TOC: "markdown-toc", PDF_DOCUMENT_TOC: "pdf-document-toc", PDF_DOCUMENT: "pdf-document", PDF_MERMAID_DIAGRAM: "pdf-mermaid-diagram", PDF_STYLE: "pdf-style", PDF_TABLE: "pdf-table", CONFIG_JSON: "config-json" }; // src/creators/createHtml.ts var import_consola2 = __toESM(require("consola")); var import_pathe4 = __toESM(require("pathe")); async function getTables(option, renderData, outputDir) { if (!option.components.includes(CE_OUTPUT_COMPONENT.TABLE)) { return []; } const renderer = container.resolve(SymbolTemplateRenderer); const rawTables = await renderer.evaluate(CE_TEMPLATE_NAME.HTML_DOCUMENT, renderData); const prettiedTables = await applyPrettier(rawTables, "html", option.prettierConfig); const tablesFileName = import_pathe4.default.join(outputDir, CE_DEFAULT_VALUE.HTML_INDEX_FILENAME); return [ { dirname: import_pathe4.default.resolve(outputDir), filename: import_pathe4.default.resolve(tablesFileName), content: prettiedTables } ]; } async function getDiagram(option, renderData, outputDir) { if (!option.components.includes(CE_OUTPUT_COMPONENT.ER)) { return []; } const renderer = container.resolve(SymbolTemplateRenderer); const rawDiagram = await renderer.evaluate(CE_TEMPLATE_NAME.HTML_MERMAID, renderData); const prettiedDiagram = await applyPrettier(rawDiagram, "html", option.prettierConfig); const diagramFileName = option.components.includes(CE_OUTPUT_COMPONENT.TABLE) ? import_pathe4.default.join(outputDir, CE_DEFAULT_VALUE.HTML_MERMAID_FILENAME) : import_pathe4.default.join(outputDir, CE_DEFAULT_VALUE.HTML_INDEX_FILENAME); return [ { dirname: import_pathe4.default.resolve(outputDir), filename: import_pathe4.default.resolve(diagramFileName), content: prettiedDiagram } ]; } async function createHtml(option, renderData) { const outputDir = await getOutputDirPath(option, getCwd(process.env)); import_consola2.default.info(`export component: ${option.components.join(", ")}`); const documents = (await Promise.all( option.components.map(async (component) => { if (component === CE_OUTPUT_COMPONENT.TABLE) { return getTables(option, renderData, outputDir); } if (component === CE_OUTPUT_COMPONENT.ER) { return getDiagram(option, renderData, outputDir); } return []; }) )).flat(); return documents; } // src/creators/createImageHtml.ts var import_my_node_fp3 = require("my-node-fp"); var import_node_crypto = require("crypto"); var import_pathe5 = __toESM(require("pathe")); async function createImageHtml(option, renderData) { const renderer = container.resolve(SymbolTemplateRenderer); const rawHtml = await renderer.evaluate(CE_TEMPLATE_NAME.IMAGE_DOCUMENT, { ...renderData, option: { ...renderData.option, width: "200vw" } }); const prettiedHtml = await applyPrettier(rawHtml, "html", option.prettierConfig); const outputDirPath = option.output != null ? import_pathe5.default.resolve(option.output) : process.cwd(); await betterMkdir(outputDirPath); const tempFileName = import_pathe5.default.join(outputDirPath, `${(0, import_node_crypto.randomUUID)()}.html`); return { dirname: await (0, import_my_node_fp3.getDirname)(outputDirPath), content: prettiedHtml, filename: import_pathe5.default.resolve(tempFileName) }; } // src/creators/createMarkdown.ts var import_pathe6 = __toESM(require("pathe")); async function createMarkdown(option, renderData) { const renderer = container.resolve(SymbolTemplateRenderer); const rawMarkdown = await renderer.evaluate(CE_TEMPLATE_NAME.MARKDOWN_DOCUMENT, renderData); const prettiedMarkdown = await applyPrettier(rawMarkdown, "md", option.prettierConfig); const markdownFileName = `${renderData.metadata.name}.md`; const outputDir = await getOutputDirPath(option, getCwd(process.env)); return { filename: import_pathe6.default.resolve(import_pathe6.default.join(outputDir, markdownFileName)), dirname: import_pathe6.default.resolve(outputDir), content: prettiedMarkdown }; } // src/creators/createPdfHtml.ts var import_my_node_fp4 = require("my-node-fp"); var import_node_crypto2 = require("crypto"); var import_pathe7 = __toESM(require("pathe")); async function createPdfHtml(option, renderData) { const renderer = container.resolve(SymbolTemplateRenderer); const rawHtml = await renderer.evaluate(CE_TEMPLATE_NAME.PDF_DOCUMENT, renderData); const prettiedHtml = await applyPrettier(rawHtml, "html", option.prettierConfig); const outputDirPath = option.output != null ? import_pathe7.default.resolve(option.output) : process.cwd(); await betterMkdir(outputDirPath); const tempFileName = import_pathe7.default.join(outputDirPath, `${(0, import_node_crypto2.randomUUID)()}.html`); return { dirname: await (0, import_my_node_fp4.getDirname)(outputDirPath), content: prettiedHtml, filename: import_pathe7.default.resolve(tempFileName) }; } // src/configs/const-enum/CE_OUTPUT_FORMAT.ts var CE_OUTPUT_FORMAT = { HTML: "html", MARKDOWN: "md", PDF: "pdf", IMAGE: "image" }; // src/databases/const-enum/CE_RECORD_KIND.ts var CE_RECORD_KIND = { COLUMN: "column", ENTITY: "entity", RELATION: "relation", INDEX: "index" }; // src/modules/getSlashEndRoutePath.ts function getSlashEndRoutePath(basePath) { if (basePath.endsWith("/")) { return basePath; } return `${basePath}/`; } // src/creators/getRenderData.ts var import_alasql = __toESM(require("alasql")); var import_compare_versions = require("compare-versions"); async function getRenderData(records, metadata, option) { const versionRows = await import_alasql.default.promise("SELECT DISTINCT version FROM ?", [records]); const unSortedVersions = versionRows.map((version) => version.version); const versions = option.versionFrom === "timestamp" ? unSortedVersions.sort((l, r) => r.localeCompare(l)) : unSortedVersions.sort((l, r) => (0, import_compare_versions.compareVersions)(r, l)); const renderDatas = await Promise.all( versions.map(async (version) => { const entities = await import_alasql.default.promise(`SELECT * FROM ? WHERE [$kind] = ? AND version = ?`, [ records, "entity", version ]); const renderData = await Promise.all( entities.map(async (entity) => { const [columns, relations, indices] = await Promise.all([ await import_alasql.default.promise("SELECT * FROM ? WHERE [$kind] = ? AND entity = ? AND version = ?", [ records, CE_RECORD_KIND.COLUMN, entity.entity, version ]), await import_alasql.default.promise("SELECT * FROM ? WHERE [$kind] = ? AND entity = ? AND version = ?", [ records, CE_RECORD_KIND.RELATION, entity.entity, version ]), await import_alasql.default.promise("SELECT * FROM ? WHERE [$kind] = ? AND entity = ? AND version = ?", [ records, CE_RECORD_KIND.INDEX, entity.entity, version ]) ]); return { ...entity, columns, relations, indices }; }) ); return { version, entities: renderData, latest: version === metadata.version }; }) ); if (option.format === CE_OUTPUT_FORMAT.HTML) { return { versions: renderDatas, option: { ...option, routeBasePath: option.routeBasePath != null ? getSlashEndRoutePath(option.routeBasePath) : void 0 }, metadata }; } return { versions: renderDatas, option, metadata }; } // src/modules/getPuppeteerConfig.ts var import_fs2 = __toESM(require("fs")); var import_jsonc_parser2 = require("jsonc-parser"); var import_my_node_fp5 = require("my-node-fp"); async function getPuppeteerConfig(confgFilePath) { try { if (confgFilePath == null) { return {}; } if (await (0, import_my_node_fp5.exists)(confgFilePath)) { const buf = await import_fs2.default.promises.readFile(confgFilePath); const option = (0, import_jsonc_parser2.parse)(buf.toString()); return option; } return {}; } catch { return {}; } } // src/creators/writeToImage.ts var import_consola3 = __toESM(require("consola")); var import_del = __toESM(require("del")); var import_my_easy_fp4 = require("my-easy-fp"); var import_node_fs2 = __toESM(require("fs")); var import_pathe8 = __toESM(require("pathe")); var puppeteer = __toESM(require("puppeteer")); async function writeToImage(document, option, renderData) { let localBrowser; let localPage; try { const puppeteerConfig = await getPuppeteerConfig(option.prettierConfig); const browser = await puppeteer.launch({ ...puppeteerConfig, headless: true }); const page = await browser.newPage(); const puppeteerGotoOption = { waitUntil: "domcontentloaded", timeout: 6e4 }; localBrowser = browser; localPage = page; await betterMkdir(document.filename); await import_node_fs2.default.promises.writeFile(document.filename, document.content); await page.setViewport({ width: option.viewportWidth ?? 1280, height: option.viewportHeight ?? 720 * 2 }); await page.goto(`file://${document.filename}`, puppeteerGotoOption); import_consola3.default.debug(`file write start: ${document.filename}`); await page.$eval( "body", (body, backgroundColor) => { body.style.background = backgroundColor; }, option.backgroundColor ?? "white" ); if (option.imageFormat === "svg") { const svg = await page.$eval("#mermaid-diagram-container", (container2) => container2.innerHTML); if (svg == null) { await (0, import_del.default)(document.filename); throw new Error("invalid image html document template"); } await import_node_fs2.default.promises.writeFile(import_pathe8.default.join(document.dirname, `${renderData.metadata.name}.svg`), svg); import_consola3.default.debug("file write end"); await (0, import_del.default)(document.filename); import_consola3.default.info(`Component ER diagram successfully write on ${renderData.metadata.name}.svg`); return [import_pathe8.default.join(document.dirname, `${renderData.metadata.name}.svg`)]; } const clip = await page.$eval("svg", (htmlSvgElement) => { const react = htmlSvgElement.getBoundingClientRect(); return { x: react.left, y: react.top, width: react.width, height: react.height }; }); await page.screenshot({ path: import_pathe8.default.join(document.dirname, `${renderData.metadata.name}.png`), clip, omitBackground: false }); import_consola3.default.debug("file write end"); await (0, import_del.default)(document.filename); import_consola3.default.info(`Component ER diagram successfully write on ${renderData.metadata.name}.png`); return [import_pathe8.default.join(document.dirname, `${renderData.metadata.name}.png`)]; } catch (caught) { const err = (0, import_my_easy_fp4.isError)(caught, new Error("unknown error raised from writeToImage")); import_consola3.default.error(err.message); import_consola3.default.error(err.stack); return false; } finally { import_consola3.default.debug("Start page, brower close"); if (localPage !== void 0 && localPage !== null) { await localPage.close(); } if (localBrowser !== void 0 && localBrowser !== null) { await localBrowser.close(); } } } // src/creators/writeToPdf.ts var import_consola4 = __toESM(require("consola")); var import_del2 = __toESM(require("del")); var import_fs3 = __toESM(require("fs")); var import_my_easy_fp5 = require("my-easy-fp"); var import_pathe9 = __toESM(require("pathe")); var puppeteer2 = __toESM(require("puppeteer")); async function writeToPdf(document, option, renderData) { let localBrowser; let localPage; try { const puppeteerConfig = await getPuppeteerConfig(option.puppeteerConfig); const browser = await puppeteer2.launch({ ...puppeteerConfig, headless: true }); const page = await browser.newPage(); const puppeteerGotoOption = { waitUntil: "domcontentloaded", timeout: 6e4 }; localBrowser = browser; localPage = page; import_consola4.default.info("filename: ", document.filename); await page.setViewport({ width: option.viewportWidth ?? 1280, height: option.viewportHeight ?? 720 * 2 }); await import_fs3.default.promises.writeFile(document.filename, document.content); await page.goto(`file://${document.filename}`, puppeteerGotoOption); await page.pdf({ path: import_pathe9.default.join(document.dirname, `${renderData.metadata.name}.pdf`), printBackground: option.backgroundColor !== "transparent" }); await (0, import_del2.default)(document.filename); return [import_pathe9.default.join(document.dirname, `${renderData.metadata.name}.pdf`)]; } catch (caught) { const err = (0, import_my_easy_fp5.isError)(caught, new Error("unknown error raised from writeToPdf")); import_consola4.default.error(err.message); import_consola4.default.error(err.stack); return []; } finally { if (localPage !== void 0 && localPage !== null) { import_consola4.default.debug("Session Closed"); await localPage.close(); } if (localBrowser !== void 0 && localBrowser !== null) { await localBrowser.close(); } } } // src/databases/const-enum/CE_CHANGE_KIND.ts var CE_CHANGE_KIND = { CHANGE: "change", ADD: "add", DELETE: "delete", NONE: "none" }; // src/databases/compareDatabase.ts var import_deep_object_diff = require("deep-object-diff"); var import_my_easy_fp6 = require("my-easy-fp"); function compareDatabase(metadata, next, prev) { if (prev.length <= 0) { return next.map((record) => ({ ...record, change: CE_CHANGE_KIND.NONE })); } const nextMap = next.reduce((aggregation, record) => { switch (record.$kind) { case CE_RECORD_KIND.ENTITY: return { ...aggregation, [getEntityHash(record)]: record }; case CE_RECORD_KIND.COLUMN: return { ...aggregation, [getColumnHash(record)]: record }; case CE_RECORD_KIND.RELATION: return { ...aggregation, [getRelationHash(record)]: record }; case CE_RECORD_KIND.INDEX: return { ...aggregation, [getIndexHash(record)]: record }; default: return aggregation; } }, {}); const prevMap = prev.reduce((aggregation, record) => { switch (record.$kind) { case CE_RECORD_KIND.ENTITY: return { ...aggregation, [getEntityHash(record)]: record }; case CE_RECORD_KIND.COLUMN: return { ...aggregation, [getColumnHash(record)]: record }; case CE_RECORD_KIND.RELATION: return { ...aggregation, [getRelationHash(record)]: record }; case CE_RECORD_KIND.INDEX: return { ...aggregation, [getIndexHash(record)]: record }; default: return aggregation; } }, {}); const compared = (0, import_my_easy_fp6.settify)([...Object.keys(nextMap), ...Object.keys(prevMap)]).map((key) => { const fromNext = nextMap[key]; const fromPrev = prevMap[key]; if (fromNext != null && fromPrev == null) { return { ...fromNext, change: CE_CHANGE_KIND.ADD }; } if (fromNext == null && fromPrev != null) { return { ...fromPrev, change: CE_CHANGE_KIND.DELETE, version: metadata.version }; } const forCompareNext = { ...fromNext, title: fromNext.title ?? "", change: CE_CHANGE_KIND.NONE, createdAt: "", updatedAt: "", version: "" }; const forComparePrev = { ...fromPrev, title: fromPrev.title ?? "", change: CE_CHANGE_KIND.NONE, createdAt: "", updatedAt: "", version: "" }; const diffed = (0, import_deep_object_diff.detailedDiff)(forCompareNext, forComparePrev); if (Object.keys(diffed.added).length <= 0 && Object.keys(diffed.updated).length <= 0 && Object.keys(diffed.deleted).length <= 0) { return { ...fromNext, change: CE_CHANGE_KIND.NONE }; } return { ...fromNext, change: CE_CHANGE_KIND.CHANGE, prev: fromPrev }; }); return compared; } // src/databases/flushDatabase.ts var import_node_fs3 = __toESM(require("fs")); var import_pathe10 = __toESM(require("pathe")); async function flushDatabase(option, records) { const dirname = await getOutputDirPath({ output: option.databasePath }, process.cwd()); const filename = import_pathe10.default.join(dirname, CE_DEFAULT_VALUE.DATABASE_FILENAME); if (filename == null) { throw new Error(`invalid database name: undefined`); } await import_node_fs3.default.promises.writeFile( import_pathe10.default.join(dirname, CE_DEFAULT_VALUE.DATABASE_FILENAME), JSON.stringify(records, void 0, 2) ); return records; } // src/databases/openDatabase.ts var import_jsonc_parser3 = require("jsonc-parser"); var import_my_easy_fp7 = require("my-easy-fp"); var import_my_node_fp6 = require("my-node-fp"); var import_node_fs4 = __toESM(require("fs")); var import_pathe11 = __toESM(require("pathe")); async function openDatabase(option) { const dirname = await getOutputDirPath({ output: option.databasePath }, process.cwd()); const filename = import_pathe11.default.join(dirname, CE_DEFAULT_VALUE.DATABASE_FILENAME); if (filename == null) { return []; } if ((0, import_my_easy_fp7.isFalse)(await (0, import_my_node_fp6.exists)(filename))) { return []; } const db = (0, import_jsonc_parser3.parse)((await import_node_fs4.default.promises.readFile(filename)).toString()); return db; } // src/databases/processDatabase.ts var import_alasql2 = __toESM(require("alasql")); var import_compare_versions2 = require("compare-versions"); var import_my_easy_fp8 = require("my-easy-fp"); async function processDatabase(metadata, db, option) { if (db.length <= 0) { return { next: [], deleted: [], prev: [] }; } const currentVersion = metadata.version; const versions = await import_alasql2.default.promise("SELECT DISTINCT version FROM ?", [db]); const sortedVersions = option.versionFrom === "timestamp" ? versions.sort((l, r) => r.version.localeCompare(l.version)) : versions.sort((l, r) => (0, import_compare_versions2.compareVersions)(r.version, l.version)); const firstVersionFromDb = (0, import_my_easy_fp8.atOrThrow)(sortedVersions, 0).version; if (currentVersion !== firstVersionFromDb) { const latestRecords2 = await import_alasql2.default.promise("SELECT * FROM ? WHERE version = ?", [ db, firstVersionFromDb ]); return { next: db, deleted: [], prev: latestRecords2 }; } if (sortedVersions.length <= 1) { return { next: [], deleted: [], prev: [] }; } const secondVersionFromDb = (0, import_my_easy_fp8.atOrThrow)(sortedVersions, 1).version; const partialRecords = await import_alasql2.default.promise("SELECT * FROM ? WHERE version != ?", [ db, currentVersion ]); const oldRecords = await import_alasql2.default.promise("SELECT * FROM ? WHERE version = ?", [ db, currentVersion ]); const latestRecords = await import_alasql2.default.promise("SELECT * FROM ? WHERE version = ?", [ db, secondVersionFromDb ]); return { next: partialRecords, deleted: oldRecords, prev: latestRecords }; } // src/templates/TemplateRenderer.ts var import_consola5 = __toESM(require("consola")); var import_eta = require("eta"); var import_my_easy_fp9 = require("my-easy-fp"); var _eta, _templates, _defaultTemplates; var TemplateRenderer = class { constructor(templates, defaultTemplates) { __privateAdd(this, _eta, void 0); __privateAdd(this, _templates, void 0); __privateAdd(this, _defaultTemplates, void 0); __privateSet(this, _templates, templates); __privateSet(this, _defaultTemplates, defaultTemplates); __privateSet(this, _eta, new import_eta.Eta({ views: "erdia", autoEscape: false })); __privateGet(this, _eta).resolvePath = (templatePath) => templatePath; __privateGet(this, _eta).readFile = (templatePath) => { const template = __privateGet(this, _templates).get(templatePath) ?? __privateGet(this, _defaultTemplates).get(templatePath); return (0, import_my_easy_fp9.orThrow)(template, new Error(`cannot found template: ${templatePath}`)); }; } async evaluate(name, data) { try { const rendered = __privateGet(this, _eta).render(name, data); return rendered; } catch (caught) { const err = (0, import_my_easy_fp9.isError)(caught, new Error(`raise error from evaluateTemplate: ${name}`)); import_consola5.default.error(`template: ${name}`, data); import_consola5.default.error(err); throw err; } } }; _eta = new WeakMap(); _templates = new WeakMap(); _defaultTemplates = new WeakMap(); // src/typeorm/loadDataSource.ts var import_my_easy_fp10 = require("my-easy-fp"); var import_typeorm = require("typeorm"); var import_ImportUtils = require("typeorm/util/ImportUtils"); async function loadDataSource(dataSourceFilePath) { let dataSourceFileExports; try { [dataSourceFileExports] = await (0, import_ImportUtils.importOrRequireFile)(dataSourceFilePath); } catch (caught) { const err = (0, import_my_easy_fp10.isError)(caught, new Error(`Unable to open file: "${dataSourceFilePath}".`)); throw new Error(`Unable to open file: "${dataSourceFilePath}". ${err.message}`); } if (!dataSourceFileExports || typeof dataSourceFileExports !== "object") { throw new Error(`Given data source file must contain export of a DataSource instance`); } if (import_typeorm.InstanceChecker.isDataSource(dataSourceFileExports)) { return dataSourceFileExports; } const dataSourceExports = []; for (const fileExportKey in dataSourceFileExports) { const fileExport = dataSourceFileExports[fileExportKey]; const awaitedFileExport = await fileExport; if (import_typeorm.InstanceChecker.isDataSource(awaitedFileExport)) { dataSourceExports.push(awaitedFileExport); } } if (dataSourceExports.length === 0) { throw new Error(`Given data source file must contain export of a DataSource instance`); } if (dataSourceExports.length > 1) { throw new Error(`Given data source file must contain only one export of DataSource instance`); } return dataSourceExports[0]; } // src/typeorm/getDataSource.ts var import_my_easy_fp11 = require("my-easy-fp"); var import_my_node_fp7 = require("my-node-fp"); var import_pathe12 = __toESM(require("pathe")); async function getDataSource(options) { const dataSourcePath = import_pathe12.default.resolve(options.dataSourcePath); if ((0, import_my_easy_fp11.isFalse)(await (0, import_my_node_fp7.exists)(dataSourcePath))) { throw new Error(`Cannot found dataSource: ${dataSourcePath}`); } const dataSource = await loadDataSource(dataSourcePath); if (dataSource == null) { throw new Error(`Cannot found dataSource in ${options.dataSourcePath}`); } return dataSource; } // src/cli/builders/buildOptionBuilder.ts function buildOptionBuilder(args) { args.option("route-base-path", { describe: "define the route base path. The route base path is used as the base path for navbar anchor when generating HTML documents", type: "string", default: void 0 }).option("title", { describe: "define what will be written in the HTML document title tag", type: "string", default: void 0 }).option("prettier-config", { describe: "define the path to the prettier configuration file", type: "string", default: void 0 }).option("puppeteer-config", { describe: "define the path to the puppeteer configuration file", type: "string" }).option("width", { describe: "define the ER diagram width. The width is defined by the HTML document css attribute width", type: "string", default: "100%" }).option("viewport-width", { describe: "define the viewport width to puppeteer. The width is defined by the HTML document css attribute width", type: "number", default: 1280 }).option("viewport-height", { describe: "define the viewport height to puppeteer. The width is defined by the HTML document css attribute height", type: "number", default: 720 * 2 }).option("image-format", { describe: "define the format to image file", type: "string", choices: ["svg", "png"], default: "svg" }).option("background-color", { describe: "define the background color to html documents. eg. transparent, red, '#F0F0F0'", type: "string", default: "white" }); return args; } // src/cli/builders/outputOptionBuilder.ts function outputOptionBuilder(args) { args.option("output", { alias: "o", describe: "define the directory to output file", type: "string" }); return args; } // src/cli/builders/commonOptionBuilder.ts function commonOptionBuilder(args) { outputOptionBuilder(args).option("config", { alias: "c", describe: "define the path to to configuration file: .erdiarc", type: "string" }).option("data-source-path", { alias: "d", describe: "define the path to TypeORM data source file", type: "string" }).option("show-logo", { describe: "define the logo display on cli interface", type: "boolean", default: false }).demandOption("data-source-path"); return args; } // src/configs/const-enum/CE_ENTITY_VERSION_FROM.ts var CE_ENTITY_VERSION_FROM = { TIMESTAMP: "timestamp", PACKAGE_JSON: "package.json", FILE: "file" }; // src/configs/const-enum/CE_MERMAID_THEME.ts var CE_MERMAID_THEME = { DEFAULT: "default", FOREST: "forest", DARK: "dark", NEUTRAL: "neutral", NULL: "null" }; // src/cli/builders/documentOptionBuilder.ts function documentOptionBuilder(args) { args.option("components", { alias: "t", describe: "define the output component to builded documents", choices: [CE_OUTPUT_COMPONENT.TABLE, CE_OUTPUT_COMPONENT.ER], type: "array", default: [CE_OUTPUT_COMPONENT.TABLE, CE_OUTPUT_COMPONENT.ER] }).option("project-name", { describe: "define whether project name will come from the `package.json` name field or database name", type: "string", choices: [CE_PROJECT_NAME_FROM.APPLICATION, CE_PROJECT_NAME_FROM.DATABASE], default: CE_PROJECT_NAME_FROM.APPLICATION }).option("database-path", { describe: "define the directory to store `erdiadb.json`", type: "string", default: void 0 }).option("template-path", { describe: "define the directory to ETA templates", type: "string" }).option("skip-image-in-html", { describe: "enabling the this option will skip attaching the ER diagram image file to the html document", type: "boolean", default: false }).option("format", { describe: "define the output format to builded documents", choices: [CE_OUTPUT_FORMAT.HTML, CE_OUTPUT_FORMAT.MARKDOWN, CE_OUTPUT_FORMAT.PDF, CE_OUTPUT_FORMAT.IMAGE], type: "string", default: CE_OUTPUT_FORMAT.HTML }).option("version-from", { describe: "define whether document version will come from the `package.json` version field or specific file, timestamp", choices: [CE_ENTITY_VERSION_FROM.PACKAGE_JSON, CE_ENTITY_VERSION_FROM.FILE, CE_ENTITY_VERSION_FROM.TIMESTAMP], type: "string", default: CE_ENTITY_VERSION_FROM.PACKAGE_JSON }).option("version-path", { describe: "If the versionFrom option set `file`, read the file from this path", type: "string", default: void 0 }).option("theme", { describe: "define the mermaid.js plugin theme. see https://mermaid-js.github.io/mermaid/#/Setup?id=theme", choices: [ CE_MERMAID_THEME.DEFAULT, CE_MERMAID_THEME.DARK, CE_MERMAID_THEME.FOREST, CE_MERMAID_THEME.DARK, CE_MERMAID_THEME.NEUTRAL, CE_MERMAID_THEME.NULL ], default: CE_MERMAID_THEME.DARK, type: "string" }); return args; } // src/modules/containers/keys/SymbolDefaultTemplate.ts var SymbolDefaultTemplate = Symbol("default-template"); // src/modules/containers/keys/SymbolLogger.ts var SymbolLogger = Symbol("symbol-logger"); // src/modules/containers/keys/SymbolTemplate.ts var SymbolTemplate = Symbol("template"); // src/modules/loggers/Logger.ts var import_consola6 = __toESM(require("consola")); var _enable; var Logger = class { constructor(enable) { __privateAdd(this, _enable, void 0); __publicField(this, "info", this.logging.bind(this, "info")); __publicField(this, "warn", this.logging.bind(this, "warn")); __publicField(this, "silent", this.logging.bind(this, "silent")); __publicField(this, "error", this.logging.bind(this, "error")); __publicField(this, "success", this.logging.bind(this, "success")); __publicField(this, "fail", this.logging.bind(this, "fail")); __publicField(this, "fatal", this.logging.bind(this, "fatal")); __publicField(this, "debug", this.logging.bind(this, "debug")); __publicField(this, "trace", this.logging.bind(this, "trace")); __publicField(this, "verbose", this.logging.bind(this, "verbose")); __publicField(this, "ready", this.logging.bind(this, "ready")); __publicField(this, "box", this.logging.bind(this, "box")); __publicField(this, "log", this.logging.bind(this, "log")); __privateSet(this, _enable, enable ?? false); } get enable() { return __privateGet(this, _enable); } set enable(value) { __privateSet(this, _enable, value); } get level() { return import_consola6.default.level; } set level(level) { import_consola6.default.level = level; } logging(level, message, ...args) { if (__privateGet(this, _enable)) { import_consola6.default[level](message, ...args); } } }; _enable = new WeakMap(); // src/modules/loggers/createLogger.ts var import_awilix2 = require("awilix"); function createLogger(enable) { if (!container.hasRegistration(SymbolLogger)) { const logger = new Logger(enable ?? false); container.register(SymbolLogger, (0, import_awilix2.asValue)(logger)); } } // src/templates/modules/configTemplate.ts var configTemplate = ` { // directory for output files "output": "<%= it.config.output %>", // typeorm dataSourcePath "data-source-path": "<%= it.config.dataSourceFile %>", // type of generated document "components": <%~ JSON.stringify(it.config.components) %>, // kind of document name // - db: database name from TypeORM // - app: application name from package.json "project-name": "<%= it.config.projectName %>", // custom template file path. erdia are using [ETA](https://eta.js.org/) template engine <% if (it.config.templatePath != null) { %> "template-path": "<%= it.config.templatePath %>", <% } else { %> // "template-path": "", <% } %> // erdia entity database file path <% if (it.config.databasePath != null) { %> "database-path": "<%= it.config.databasePath %>", <% } else { %> // "database-path": "", <% } %> // erdia entity database file path <% if (it.config.routeBasePath != null) { %> "route-base-path": "<%= it.config.routeBasePath %>", <% } else { %> // "route-base-path": "", <% } %> // document version using package.json version or timestamp "version-from": "<%= it.config.versionFrom %>", // If the versionFrom option set file, read the file from this path <% if (it.config.versionPath != null) { %> "version-path": "<%= it.config.versionPath %>", <% } else { %> // "version-path": "", <% } %> // output format of generated documents // - html // - md // - pdf // - image "format": "<%= it.config.format %>", // skip image file attachment in html document "skipImageInHtml": false, // mermaid.js plugin theme configuration // @url https://mermaid-js.github.io/mermaid/#/Setup?id=theme "theme": "<%= it.config.theme %>", // prettier config path // "prettier-config": "set your .prettierrc file path", // title tag content that inside of html document // "title": "set title tag content in html document", // ER diagram width, it will be set width css attribute // @format pdf, image <% if (it.config.format === 'pdf' || it.config.format === 'image') { -%> "width": "100%", <% } else { -%> // "width": "100%", <% } -%> // puppeteer viewport width // @format pdf, image <% if (it.config.format === 'pdf' || it.config.format === 'image') { -%> "viewport-width": 1280, <% } else { -%> // "viewport-width": 1280, <% } -%> // puppeteer viewport height // @format pdf, image <% if (it.config.format === 'pdf' || it.config.format === 'image') { -%> "viewport-height": 1440, <% } else { -%> // "viewport-height": 1440, <% } -%> // puppeteer config file path // @format pdf, image // "puppeteer-config-path": "set your puppeteer configuration file path", // Background color. Example: transparent, red, '#F0F0F0'. Optional. Default: white // @format pdf, image // "background-color": "#FFFFFF", // ER diagram export image file format // @format image <% if (it.config.format === 'image') { %> "image-format": "<%= it.config.imageFormat %>", <% } else { %> // "image-format": "svg", <% } %> } `; // src/templates/modules/getTemplateModulePath.ts var import_my_node_fp8 = require("my-node-fp"); var import_pathe13 = __toESM(require("pathe")); async function getTemplateModulePath(templatePathParam) { const currentFilePath = import_pathe13.default.resolve(__dirname); if (templatePathParam != null) { const currentWithTemplatePath = import_pathe13.default.resolve(import_pathe13.default.join(currentFilePath, templatePathParam)); if (await (0, import_my_node_fp8.exists)(currentWithTemplatePath)) { return currentWithTemplatePath; } } const pac