erdia
Version:
CLI to generate mermaid.js ER diagram using TypeORM entity
4 lines • 190 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../../src/cli/builders/buildOptionBuilder.ts", "../../src/cli/builders/outputOptionBuilder.ts", "../../src/cli/builders/commonOptionBuilder.ts", "../../src/configs/const-enum/CE_ENTITY_VERSION_FROM.ts", "../../src/configs/const-enum/CE_MERMAID_THEME.ts", "../../src/configs/const-enum/CE_OUTPUT_COMPONENT.ts", "../../src/configs/const-enum/CE_OUTPUT_FORMAT.ts", "../../src/configs/const-enum/CE_PROJECT_NAME_FROM.ts", "../../src/cli/builders/documentOptionBuilder.ts", "../../src/common/getDatabaseName.ts", "../../src/common/getPackageName.ts", "../../src/common/getProjectName.ts", "../../src/common/getFileVersion.ts", "../../src/configs/const-enum/CE_DEFAULT_VALUE.ts", "../../src/configs/modules/getCwd.ts", "../../src/modules/files/getFindFile.ts", "../../src/modules/files/betterMkdir.ts", "../../src/modules/files/getOutputDirPath.ts", "../../src/common/getVersion.ts", "../../src/modules/containers/container.ts", "../../src/modules/containers/keys/SymbolDataSource.ts", "../../src/common/getMetadata.ts", "../../src/creators/applyPretter.ts", "../../src/modules/containers/keys/SymbolTemplateRenderer.ts", "../../src/templates/cosnt-enum/CE_TEMPLATE_NAME.ts", "../../src/creators/createHtml.ts", "../../src/creators/createImageHtml.ts", "../../src/creators/createMarkdown.ts", "../../src/creators/createPdfHtml.ts", "../../src/databases/const-enum/CE_RECORD_KIND.ts", "../../src/modules/getSlashEndRoutePath.ts", "../../src/creators/getRenderData.ts", "../../src/modules/getPuppeteerConfig.ts", "../../src/creators/writeToImage.ts", "../../src/creators/writeToPdf.ts", "../../src/common/getColumnHash.ts", "../../src/common/getEntityHash.ts", "../../src/common/getIndexHash.ts", "../../src/common/getPlainRelationType.ts", "../../src/common/getRelationHash.ts", "../../src/databases/const-enum/CE_CHANGE_KIND.ts", "../../src/databases/compareDatabase.ts", "../../src/databases/flushDatabase.ts", "../../src/databases/openDatabase.ts", "../../src/databases/processDatabase.ts", "../../src/modules/containers/keys/SymbolDefaultTemplate.ts", "../../src/modules/containers/keys/SymbolLogger.ts", "../../src/modules/containers/keys/SymbolTemplate.ts", "../../src/modules/loggers/Logger.ts", "../../src/modules/loggers/createLogger.ts", "../../src/templates/TemplateRenderer.ts", "../../src/templates/modules/configTemplate.ts", "../../src/templates/modules/getTemplateModulePath.ts", "../../src/templates/modules/getTemplatePath.ts", "../../src/modules/files/getGlobFiles.ts", "../../src/modules/scopes/defaultExclude.ts", "../../src/templates/modules/getTemplate.ts", "../../src/templates/modules/getTemplates.ts", "../../src/templates/modules/loadTemplates.ts", "../../src/configs/const-enum/CE_COLUMN_ATTRIBUTE.ts", "../../src/creators/columns/getColumnWeight.ts", "../../src/typeorm/columns/getColumnAttributeKey.ts", "../../src/typeorm/columns/getIsNullable.ts", "../../src/typeorm/columns/getColumnType.ts", "../../src/typeorm/columns/getComment.ts", "../../src/typeorm/entities/getEntityName.ts", "../../src/typeorm/columns/getColumnRecord.ts", "../../src/typeorm/entities/getEntityRecord.ts", "../../src/typeorm/entities/getEntityRecords.ts", "../../src/typeorm/loadDataSource.ts", "../../src/typeorm/getDataSource.ts", "../../src/typeorm/indices/getIndexRecord.ts", "../../src/typeorm/indices/getIndexRecords.ts", "../../src/typeorm/relations/dedupeManyToManyRelationRecord.ts", "../../src/typeorm/entities/getEntityPropertyName.ts", "../../src/typeorm/relations/getInverseRelationMetadata.ts", "../../src/typeorm/relations/getManyToManyJoinColumn.ts", "../../src/typeorm/relations/getManyToOneJoinColumn.ts", "../../src/typeorm/relations/getJoinColumn.ts", "../../src/typeorm/relations/getManyToManyEntityMetadata.ts", "../../src/typeorm/relations/getRelationRecord.ts", "../../src/typeorm/relations/getRelationRecords.ts", "../../src/modules/commands/building.ts", "../../src/cli/commands/buildDocumentCommandHandler.ts", "../../src/modules/commands/cleaning.ts", "../../src/cli/commands/cleanDocumentCommandHandler.ts", "../../src/configs/const-enum/CE_IMAGE_FORMAT.ts", "../../src/configs/modules/getAutoCompleteSource.ts", "../../src/modules/files/getTemplateDirPath.ts", "../../src/modules/commands/ejecting.ts", "../../src/configs/modules/getConfigContent.ts", "../../src/modules/commands/initializing.ts", "../../src/cli/commands/initConfigCommandHandler.ts", "../../src/cli/commands/templateEjectCommandHandler.ts", "../../src/configs/const-enum/CE_COMMAND_LIST.ts", "../../src/configs/modules/getConfigFilePath.ts", "../../src/configs/modules/preLoadConfig.ts", "../../src/cli.ts"],
"sourcesContent": ["import type { Argv } from 'yargs';\n\nexport function buildOptionBuilder<T>(args: Argv<T>) {\n // option\n args\n .option('route-base-path', {\n describe:\n 'define the route base path. The route base path is used as the base path for navbar anchor when generating HTML documents',\n type: 'string',\n default: undefined,\n })\n .option('title', {\n describe: 'define what will be written in the HTML document title tag',\n type: 'string',\n default: undefined,\n })\n .option('prettier-config', {\n describe: 'define the path to the prettier configuration file',\n type: 'string',\n default: undefined,\n })\n .option('puppeteer-config', {\n describe: 'define the path to the puppeteer configuration file',\n type: 'string',\n })\n .option('width', {\n describe: 'define the ER diagram width. The width is defined by the HTML document css attribute width',\n type: 'string',\n default: '100%',\n })\n .option('viewport-width', {\n describe: 'define the viewport width to puppeteer. The width is defined by the HTML document css attribute width',\n type: 'number',\n default: 1280,\n })\n .option('viewport-height', {\n describe:\n 'define the viewport height to puppeteer. The width is defined by the HTML document css attribute height',\n type: 'number',\n default: 720 * 2,\n })\n .option('image-format', {\n describe: 'define the format to image file',\n type: 'string',\n choices: ['svg', 'png'],\n default: 'svg',\n })\n .option('background-color', {\n describe: \"define the background color to html documents. eg. transparent, red, '#F0F0F0'\",\n type: 'string',\n default: 'white',\n });\n\n return args;\n}\n", "import type { Argv } from 'yargs';\n\nexport function outputOptionBuilder<T>(args: Argv<T>) {\n // option\n args.option('output', {\n alias: 'o',\n describe: 'define the directory to output file',\n type: 'string',\n });\n\n return args;\n}\n", "import { outputOptionBuilder } from '#/cli/builders/outputOptionBuilder';\nimport type { Argv } from 'yargs';\n\nexport function commonOptionBuilder<T>(args: Argv<T>) {\n // option\n outputOptionBuilder(args)\n .option('config', {\n alias: 'c',\n describe: 'define the path to to configuration file: .erdiarc',\n type: 'string',\n })\n .option('data-source-path', {\n alias: 'd',\n describe: 'define the path to TypeORM data source file',\n type: 'string',\n })\n .option('show-logo', {\n describe: 'define the logo display on cli interface',\n type: 'boolean',\n default: false,\n })\n .demandOption('data-source-path');\n\n return args;\n}\n", "/**\n * Entity document version using package.json version or specific file, timestamp\n *\n * - timestamp: use timestamp\n * - file: use version file\n * - package.json: use package.json\n */\nexport const CE_ENTITY_VERSION_FROM = {\n TIMESTAMP: 'timestamp',\n PACKAGE_JSON: 'package.json',\n FILE: 'file',\n} as const;\n\nexport type CE_ENTITY_VERSION_FROM = (typeof CE_ENTITY_VERSION_FROM)[keyof typeof CE_ENTITY_VERSION_FROM];\n", "/**\n * mermaid.js theme configuration\n *\n * @see https://mermaid-js.github.io/mermaid/#/Setup?id=theme\n *\n * - default\n * - forest\n * - dark\n * - neutral\n * - null\n */\nexport const CE_MERMAID_THEME = {\n DEFAULT: 'default',\n FOREST: 'forest',\n DARK: 'dark',\n NEUTRAL: 'neutral',\n NULL: 'null',\n} as const;\n\nexport type CE_MERMAID_THEME = (typeof CE_MERMAID_THEME)[keyof typeof CE_MERMAID_THEME];\n", "/**\n * ER Diagram output component\n *\n * - table: Entity spec.\n * - er: ER diagram\n */\nexport const CE_OUTPUT_COMPONENT = {\n TABLE: 'table',\n ER: 'er',\n} as const;\n\nexport type CE_OUTPUT_COMPONENT = (typeof CE_OUTPUT_COMPONENT)[keyof typeof CE_OUTPUT_COMPONENT];\n", "export const CE_OUTPUT_FORMAT = {\n HTML: 'html',\n MARKDOWN: 'md',\n PDF: 'pdf',\n IMAGE: 'image',\n} as const;\n\nexport type CE_OUTPUT_FORMAT = (typeof CE_OUTPUT_FORMAT)[keyof typeof CE_OUTPUT_FORMAT];\n", "/**\n * determine whether project name will come from the name in `package.json` or database name\n *\n * - db: database name from TypeORM\n * - app: application name from package.json\n */\nexport const CE_PROJECT_NAME_FROM = {\n DATABASE: 'db',\n APPLICATION: 'app',\n} as const;\n\nexport type CE_PROJECT_NAME_FROM = (typeof CE_PROJECT_NAME_FROM)[keyof typeof CE_PROJECT_NAME_FROM];\n", "import { CE_ENTITY_VERSION_FROM } from '#/configs/const-enum/CE_ENTITY_VERSION_FROM';\nimport { CE_MERMAID_THEME } from '#/configs/const-enum/CE_MERMAID_THEME';\nimport { CE_OUTPUT_COMPONENT } from '#/configs/const-enum/CE_OUTPUT_COMPONENT';\nimport { CE_OUTPUT_FORMAT } from '#/configs/const-enum/CE_OUTPUT_FORMAT';\nimport { CE_PROJECT_NAME_FROM } from '#/configs/const-enum/CE_PROJECT_NAME_FROM';\nimport type { Argv } from 'yargs';\n\nexport function documentOptionBuilder<T>(args: Argv<T>) {\n // option\n args\n .option('components', {\n alias: 't',\n describe: 'define the output component to builded documents',\n choices: [CE_OUTPUT_COMPONENT.TABLE, CE_OUTPUT_COMPONENT.ER],\n type: 'array',\n default: [CE_OUTPUT_COMPONENT.TABLE, CE_OUTPUT_COMPONENT.ER],\n })\n .option('project-name', {\n describe: 'define whether project name will come from the `package.json` name field or database name',\n type: 'string',\n choices: [CE_PROJECT_NAME_FROM.APPLICATION, CE_PROJECT_NAME_FROM.DATABASE],\n default: CE_PROJECT_NAME_FROM.APPLICATION,\n })\n .option('database-path', {\n describe: 'define the directory to store `erdiadb.json`',\n type: 'string',\n default: undefined,\n })\n .option('template-path', {\n describe: 'define the directory to ETA templates',\n type: 'string',\n })\n .option('skip-image-in-html', {\n describe: 'enabling the this option will skip attaching the ER diagram image file to the html document',\n type: 'boolean',\n default: false,\n })\n .option('format', {\n describe: 'define the output format to builded documents',\n choices: [CE_OUTPUT_FORMAT.HTML, CE_OUTPUT_FORMAT.MARKDOWN, CE_OUTPUT_FORMAT.PDF, CE_OUTPUT_FORMAT.IMAGE],\n type: 'string',\n default: CE_OUTPUT_FORMAT.HTML,\n })\n .option('version-from', {\n describe:\n 'define whether document version will come from the `package.json` version field or specific file, timestamp',\n choices: [CE_ENTITY_VERSION_FROM.PACKAGE_JSON, CE_ENTITY_VERSION_FROM.FILE, CE_ENTITY_VERSION_FROM.TIMESTAMP],\n type: 'string',\n default: CE_ENTITY_VERSION_FROM.PACKAGE_JSON,\n })\n .option('version-path', {\n describe: 'If the versionFrom option set `file`, read the file from this path',\n type: 'string',\n default: undefined,\n })\n .option('theme', {\n describe: 'define the mermaid.js plugin theme. see https://mermaid-js.github.io/mermaid/#/Setup?id=theme',\n choices: [\n CE_MERMAID_THEME.DEFAULT,\n CE_MERMAID_THEME.DARK,\n CE_MERMAID_THEME.FOREST,\n CE_MERMAID_THEME.DARK,\n CE_MERMAID_THEME.NEUTRAL,\n CE_MERMAID_THEME.NULL,\n ],\n default: CE_MERMAID_THEME.DARK,\n type: 'string',\n });\n\n return args;\n}\n", "import { TextDecoder } from 'util';\n\nexport function getDatabaseName(options: { database?: string | Uint8Array | undefined }): string {\n const name = options.database;\n\n if (typeof name === 'string') {\n return name;\n }\n\n if (name instanceof Uint8Array) {\n return new TextDecoder().decode(name);\n }\n\n return 'default';\n}\n", "export function getPackageName(json: Record<string, unknown>): string {\n const { name } = json;\n\n if (typeof name === 'string' && name !== '') {\n return name;\n }\n\n throw new Error('Cannot get project name from package.json');\n}\n", "import { getDatabaseName } from '#/common/getDatabaseName';\nimport { getPackageName } from '#/common/getPackageName';\nimport { CE_PROJECT_NAME_FROM } from '#/configs/const-enum/CE_PROJECT_NAME_FROM';\nimport type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\n\nexport async function getProjectName(\n dataSource: { options: { database?: string | Uint8Array | undefined } },\n json: Record<string, unknown>,\n option: Pick<IBuildCommandOption, 'projectName'>,\n) {\n if (option.projectName === CE_PROJECT_NAME_FROM.DATABASE) {\n if (dataSource.options.database != null) {\n const databaseName = getDatabaseName(dataSource.options);\n return databaseName;\n }\n\n const name = getPackageName(json);\n return name;\n }\n\n const name = getPackageName(json);\n return name;\n}\n", "import { parse } from 'jsonc-parser';\nimport semver from 'semver';\n\nexport function getFileVersion(buf: Buffer): string {\n const rawJson = buf.toString();\n\n if (semver.valid(rawJson)) {\n return rawJson;\n }\n\n const parsed = parse(rawJson) as Record<string, string>;\n const { version } = parsed;\n\n if (version == null || version === '') {\n throw new Error(`invalid version file: ${rawJson}`);\n }\n\n return version;\n}\n", "export const CE_DEFAULT_VALUE = {\n CONFIG_FILE_NAME: '.erdiarc',\n TSCONFIG_FILE_NAME: 'tsconfig.json',\n\n HTML_INDEX_FILENAME: 'index.html',\n HTML_MERMAID_FILENAME: 'mermaid.html',\n\n MARKDOWN_FILENAME: 'erdia.md',\n\n DATABASE_FILENAME: 'erdiadb.json',\n VERSION_FILENAME: '.erdiaverrc',\n\n TEMPLATES_PATH: 'templates',\n\n DATA_SOURCE_FILE_FUZZY_SCORE_LIMIT: 50,\n OUTPUT_DIRECTORY_FUZZY_SCORE_LIMIT: 50,\n} as const;\n\nexport type CE_DEFAULT_VALUE = (typeof CE_DEFAULT_VALUE)[keyof typeof CE_DEFAULT_VALUE];\n", "export function getCwd(env: NodeJS.ProcessEnv): string {\n if ((env.USE_INIT_CWD ?? 'false') === 'false') {\n return process.cwd();\n }\n\n if (env.INIT_CWD != null) {\n return env.INIT_CWD;\n }\n\n return process.cwd();\n}\n", "import findUp from 'find-up';\n\nexport async function getFindFile(\n filename: string | readonly string[],\n option?: findUp.Options,\n): Promise<string | undefined> {\n const finded = await findUp(filename, option);\n return finded;\n}\n", "import { isFalse } from 'my-easy-fp';\nimport { exists, getDirname } from 'my-node-fp';\nimport fs from 'node:fs';\nimport pathe from 'pathe';\n\nexport async function betterMkdir(filePath: string) {\n const isFilePathExist = await exists(filePath);\n\n if (isFalse(isFilePathExist)) {\n const extname = pathe.extname(filePath);\n const hasExtname = extname !== '' && extname.length > 0;\n\n if (hasExtname) {\n const dirPath = await getDirname(filePath);\n await fs.promises.mkdir(dirPath, { recursive: true });\n } else {\n await fs.promises.mkdir(filePath, { recursive: true });\n }\n }\n}\n", "import type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\nimport { betterMkdir } from '#/modules/files/betterMkdir';\nimport { isFalse } from 'my-easy-fp';\nimport { exists, getDirname, isDirectory } from 'my-node-fp';\nimport pathe from 'pathe';\n\nexport async function getOutputDirPath(option: Pick<IBuildCommandOption, 'output'>, cwd: string) {\n const outputDirPath = option.output ?? cwd;\n const resolvedOutputDirPath = pathe.resolve(outputDirPath);\n\n if (isFalse(await exists(resolvedOutputDirPath))) {\n await betterMkdir(pathe.join(resolvedOutputDirPath));\n return resolvedOutputDirPath;\n }\n\n if (isFalse(await isDirectory(outputDirPath))) {\n return pathe.resolve(await getDirname(outputDirPath));\n }\n\n return pathe.resolve(outputDirPath);\n}\n", "import { getFileVersion } from '#/common/getFileVersion';\nimport { CE_DEFAULT_VALUE } from '#/configs/const-enum/CE_DEFAULT_VALUE';\nimport type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\nimport { getCwd } from '#/configs/modules/getCwd';\nimport { getFindFile } from '#/modules/files/getFindFile';\nimport { getOutputDirPath } from '#/modules/files/getOutputDirPath';\nimport dayjs from 'dayjs';\nimport fs from 'fs';\nimport pathe from 'pathe';\n\nasync function getVersionFilename(\n option: Pick<IBuildCommandOption, 'versionFrom' | 'versionPath'>,\n versionFilename: string,\n) {\n if (option.versionPath != null) {\n const filename = await getFindFile(\n pathe.join(await getOutputDirPath({ output: option.versionPath }, getCwd(process.env)), versionFilename),\n { cwd: getCwd(process.env) },\n );\n\n return filename;\n }\n\n const filename = await getFindFile(versionFilename, { cwd: getCwd(process.env) });\n return filename;\n}\n\nexport async function getVersion(\n json: Record<string, unknown>,\n option: Pick<IBuildCommandOption, 'versionFrom' | 'versionPath'>,\n): Promise<{ version: string }> {\n if (option.versionFrom === 'package.json') {\n const { version } = json;\n\n if (!(typeof version === 'string') || version == null) {\n throw new Error(`Cannot found version field in package.json`);\n }\n\n return { version };\n }\n\n if (option.versionFrom === 'file') {\n const getVersionFile = async () => {\n const filename = await getVersionFilename(option, CE_DEFAULT_VALUE.VERSION_FILENAME);\n\n if (filename != null) {\n return filename;\n }\n\n const fromConfig = await getVersionFilename(option, CE_DEFAULT_VALUE.CONFIG_FILE_NAME);\n return fromConfig;\n };\n\n const versionFilename = await getVersionFile();\n\n if (versionFilename == null) {\n throw new Error(`Cannot found version file: ${CE_DEFAULT_VALUE.VERSION_FILENAME}`);\n }\n\n const versionBuf = await fs.promises.readFile(versionFilename);\n const version = getFileVersion(versionBuf);\n return { version: version.trim() };\n }\n\n return { version: `${dayjs().valueOf()}` };\n}\n", "import { createContainer } from 'awilix';\n\nexport const container = createContainer();\n", "export const SymbolDataSource = Symbol('data-source');\n", "import { getProjectName } from '#/common/getProjectName';\nimport { getVersion } from '#/common/getVersion';\nimport type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\nimport type { IRecordMetadata } from '#/databases/interfaces/IRecordMetadata';\nimport { container } from '#/modules/containers/container';\nimport { SymbolDataSource } from '#/modules/containers/keys/SymbolDataSource';\nimport dayjs from 'dayjs';\nimport filenamify from 'filenamify';\nimport readPkg from 'read-pkg';\nimport type { DataSource } from 'typeorm';\n\nexport async function getMetadata(\n option: Pick<IBuildCommandOption, 'projectName' | 'versionFrom' | 'versionPath' | 'title'>,\n): Promise<IRecordMetadata> {\n const dataSource = container.resolve<DataSource>(SymbolDataSource);\n const json = await readPkg({ normalize: false });\n const rawName = await getProjectName(dataSource, json, option);\n const name = filenamify(rawName, { replacement: '_' });\n const { version } = await getVersion(json, option);\n\n return {\n name,\n title: option.title,\n version,\n createdAt: dayjs().format(),\n updatedAt: dayjs().format(),\n };\n}\n", "import consola from 'consola';\nimport { isError } from 'my-easy-fp';\n\nexport async function applyPrettier(\n document: string,\n format: 'html' | 'md' | 'json',\n configPath?: string,\n): Promise<string> {\n try {\n const prettier = (await import('prettier')).default;\n const prettierConfig = await prettier.resolveConfig(configPath ?? '.');\n\n const formatted = await prettier.format(document, {\n ...(prettierConfig ?? {}),\n parser: format === 'md' ? 'markdown' : format,\n });\n\n return formatted;\n } catch (caught) {\n const err = isError(caught, new Error('unknown error raised from prettier appling function'));\n\n consola.error(err.message);\n consola.error(err.stack);\n\n return document;\n }\n}\n", "export const SymbolTemplateRenderer = Symbol('template-renderer');\n", "export const CE_TEMPLATE_NAME = {\n HTML_DOCUMENT_TOC: 'html-document-toc',\n HTML_DOCUMENT: 'html-document',\n HTML_MERMAID: 'html-mermaid',\n HTML_MERMAID_TOC: 'html-mermaid-toc',\n HTML_MERMAID_DIAGRAM: 'html-mermaid-diagram',\n HTML_STYLE: 'html-style',\n HTML_TABLE: 'html-table',\n\n IMAGE_DOCUMENT: 'image-document',\n IMAGE_MERMAID_DIAGRAM: 'image-mermaid-diagram',\n IMAGE_STYLE: 'image-style',\n\n MARKDOWN_DOCUMENT: 'markdown-document',\n MARKDOWN_MERMAID_DIAGRAM: 'markdown-mermaid-diagram',\n MARKDOWN_TABLE: 'markdown-table',\n MARKDOWN_TOC: 'markdown-toc',\n\n PDF_DOCUMENT_TOC: 'pdf-document-toc',\n PDF_DOCUMENT: 'pdf-document',\n PDF_MERMAID_DIAGRAM: 'pdf-mermaid-diagram',\n PDF_STYLE: 'pdf-style',\n PDF_TABLE: 'pdf-table',\n\n CONFIG_JSON: 'config-json',\n} as const;\n\nexport type CE_TEMPLATE_NAME = (typeof CE_TEMPLATE_NAME)[keyof typeof CE_TEMPLATE_NAME];\n", "import { CE_DEFAULT_VALUE } from '#/configs/const-enum/CE_DEFAULT_VALUE';\nimport { CE_OUTPUT_COMPONENT } from '#/configs/const-enum/CE_OUTPUT_COMPONENT';\nimport type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\nimport { getCwd } from '#/configs/modules/getCwd';\nimport { applyPrettier } from '#/creators/applyPretter';\nimport type { getRenderData } from '#/creators/getRenderData';\nimport type { IErdiaDocument } from '#/creators/interfaces/IErdiaDocument';\nimport { container } from '#/modules/containers/container';\nimport { SymbolTemplateRenderer } from '#/modules/containers/keys/SymbolTemplateRenderer';\nimport { getOutputDirPath } from '#/modules/files/getOutputDirPath';\nimport type { TemplateRenderer } from '#/templates/TemplateRenderer';\nimport { CE_TEMPLATE_NAME } from '#/templates/cosnt-enum/CE_TEMPLATE_NAME';\nimport consola from 'consola';\nimport pathe from 'pathe';\nimport type { AsyncReturnType } from 'type-fest';\n\nasync function getTables(\n option: Pick<IBuildCommandOption, 'output' | 'components' | 'prettierConfig'>,\n renderData: AsyncReturnType<typeof getRenderData>,\n outputDir: string,\n): Promise<IErdiaDocument[]> {\n if (!option.components.includes(CE_OUTPUT_COMPONENT.TABLE)) {\n return [];\n }\n\n const renderer = container.resolve<TemplateRenderer>(SymbolTemplateRenderer);\n const rawTables = await renderer.evaluate(CE_TEMPLATE_NAME.HTML_DOCUMENT, renderData);\n const prettiedTables = await applyPrettier(rawTables, 'html', option.prettierConfig);\n const tablesFileName = pathe.join(outputDir, CE_DEFAULT_VALUE.HTML_INDEX_FILENAME);\n return [\n {\n dirname: pathe.resolve(outputDir),\n filename: pathe.resolve(tablesFileName),\n content: prettiedTables,\n },\n ];\n}\n\nasync function getDiagram(\n option: Pick<IBuildCommandOption, 'output' | 'components' | 'prettierConfig'>,\n renderData: AsyncReturnType<typeof getRenderData>,\n outputDir: string,\n): Promise<IErdiaDocument[]> {\n if (!option.components.includes(CE_OUTPUT_COMPONENT.ER)) {\n return [];\n }\n\n const renderer = container.resolve<TemplateRenderer>(SymbolTemplateRenderer);\n const rawDiagram = await renderer.evaluate(CE_TEMPLATE_NAME.HTML_MERMAID, renderData);\n const prettiedDiagram = await applyPrettier(rawDiagram, 'html', option.prettierConfig);\n const diagramFileName = option.components.includes(CE_OUTPUT_COMPONENT.TABLE)\n ? pathe.join(outputDir, CE_DEFAULT_VALUE.HTML_MERMAID_FILENAME)\n : pathe.join(outputDir, CE_DEFAULT_VALUE.HTML_INDEX_FILENAME);\n\n return [\n {\n dirname: pathe.resolve(outputDir),\n filename: pathe.resolve(diagramFileName),\n content: prettiedDiagram,\n },\n ];\n}\n\nexport async function createHtml(\n option: Pick<IBuildCommandOption, 'output' | 'components' | 'prettierConfig'>,\n renderData: AsyncReturnType<typeof getRenderData>,\n) {\n const outputDir = await getOutputDirPath(option, getCwd(process.env));\n\n consola.info(`export component: ${option.components.join(', ')}`);\n\n const documents = (\n await Promise.all(\n option.components.map(async (component) => {\n if (component === CE_OUTPUT_COMPONENT.TABLE) {\n return getTables(option, renderData, outputDir);\n }\n\n if (component === CE_OUTPUT_COMPONENT.ER) {\n return getDiagram(option, renderData, outputDir);\n }\n\n return [];\n }),\n )\n ).flat();\n\n return documents;\n}\n", "import type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\nimport { applyPrettier } from '#/creators/applyPretter';\nimport type { getRenderData } from '#/creators/getRenderData';\nimport type { IErdiaDocument } from '#/creators/interfaces/IErdiaDocument';\nimport { container } from '#/modules/containers/container';\nimport { SymbolTemplateRenderer } from '#/modules/containers/keys/SymbolTemplateRenderer';\nimport { betterMkdir } from '#/modules/files/betterMkdir';\nimport type { TemplateRenderer } from '#/templates/TemplateRenderer';\nimport { CE_TEMPLATE_NAME } from '#/templates/cosnt-enum/CE_TEMPLATE_NAME';\nimport { getDirname } from 'my-node-fp';\nimport { randomUUID } from 'node:crypto';\nimport pathe from 'pathe';\nimport type { AsyncReturnType } from 'type-fest';\n\nexport async function createImageHtml(\n option: Pick<IBuildCommandOption, 'output' | 'components' | 'prettierConfig'>,\n renderData: AsyncReturnType<typeof getRenderData>,\n): Promise<IErdiaDocument> {\n const renderer = container.resolve<TemplateRenderer>(SymbolTemplateRenderer);\n const rawHtml = await renderer.evaluate(CE_TEMPLATE_NAME.IMAGE_DOCUMENT, {\n ...renderData,\n option: { ...renderData.option, width: '200vw' },\n });\n\n const prettiedHtml = await applyPrettier(rawHtml, 'html', option.prettierConfig);\n const outputDirPath = option.output != null ? pathe.resolve(option.output) : process.cwd();\n await betterMkdir(outputDirPath);\n const tempFileName = pathe.join(outputDirPath, `${randomUUID()}.html`);\n\n return {\n dirname: await getDirname(outputDirPath),\n content: prettiedHtml,\n filename: pathe.resolve(tempFileName),\n } satisfies IErdiaDocument;\n}\n", "import type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\nimport { getCwd } from '#/configs/modules/getCwd';\nimport { applyPrettier } from '#/creators/applyPretter';\nimport type { getRenderData } from '#/creators/getRenderData';\nimport type { IErdiaDocument } from '#/creators/interfaces/IErdiaDocument';\nimport { container } from '#/modules/containers/container';\nimport { SymbolTemplateRenderer } from '#/modules/containers/keys/SymbolTemplateRenderer';\nimport { getOutputDirPath } from '#/modules/files/getOutputDirPath';\nimport type { TemplateRenderer } from '#/templates/TemplateRenderer';\nimport { CE_TEMPLATE_NAME } from '#/templates/cosnt-enum/CE_TEMPLATE_NAME';\nimport pathe from 'pathe';\nimport type { AsyncReturnType } from 'type-fest';\n\nexport async function createMarkdown(\n option: Pick<IBuildCommandOption, 'output' | 'prettierConfig'>,\n renderData: AsyncReturnType<typeof getRenderData>,\n): Promise<IErdiaDocument> {\n const renderer = container.resolve<TemplateRenderer>(SymbolTemplateRenderer);\n const rawMarkdown = await renderer.evaluate(CE_TEMPLATE_NAME.MARKDOWN_DOCUMENT, renderData);\n const prettiedMarkdown = await applyPrettier(rawMarkdown, 'md', option.prettierConfig);\n const markdownFileName = `${renderData.metadata.name}.md`;\n const outputDir = await getOutputDirPath(option, getCwd(process.env));\n\n return {\n filename: pathe.resolve(pathe.join(outputDir, markdownFileName)),\n dirname: pathe.resolve(outputDir),\n content: prettiedMarkdown,\n } satisfies IErdiaDocument;\n}\n", "import type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\nimport { applyPrettier } from '#/creators/applyPretter';\nimport type { getRenderData } from '#/creators/getRenderData';\nimport type { IErdiaDocument } from '#/creators/interfaces/IErdiaDocument';\nimport { container } from '#/modules/containers/container';\nimport { SymbolTemplateRenderer } from '#/modules/containers/keys/SymbolTemplateRenderer';\nimport { betterMkdir } from '#/modules/files/betterMkdir';\nimport type { TemplateRenderer } from '#/templates/TemplateRenderer';\nimport { CE_TEMPLATE_NAME } from '#/templates/cosnt-enum/CE_TEMPLATE_NAME';\nimport { getDirname } from 'my-node-fp';\nimport { randomUUID } from 'node:crypto';\nimport pathe from 'pathe';\nimport type { AsyncReturnType } from 'type-fest';\n\nexport async function createPdfHtml(\n option: Pick<IBuildCommandOption, 'output' | 'components' | 'prettierConfig'>,\n renderData: AsyncReturnType<typeof getRenderData>,\n) {\n const renderer = container.resolve<TemplateRenderer>(SymbolTemplateRenderer);\n const rawHtml = await renderer.evaluate(CE_TEMPLATE_NAME.PDF_DOCUMENT, renderData);\n const prettiedHtml = await applyPrettier(rawHtml, 'html', option.prettierConfig);\n const outputDirPath = option.output != null ? pathe.resolve(option.output) : process.cwd();\n await betterMkdir(outputDirPath);\n\n const tempFileName = pathe.join(outputDirPath, `${randomUUID()}.html`);\n\n return {\n dirname: await getDirname(outputDirPath),\n content: prettiedHtml,\n filename: pathe.resolve(tempFileName),\n } satisfies IErdiaDocument;\n}\n", "export const CE_RECORD_KIND = {\n COLUMN: 'column',\n ENTITY: 'entity',\n RELATION: 'relation',\n INDEX: 'index',\n} as const;\n\nexport type CE_RECORD_KIND = (typeof CE_RECORD_KIND)[keyof typeof CE_RECORD_KIND];\n", "export function getSlashEndRoutePath(basePath: string): string {\n if (basePath.endsWith('/')) {\n return basePath;\n }\n\n return `${basePath}/`;\n}\n", "import { CE_OUTPUT_FORMAT } from '#/configs/const-enum/CE_OUTPUT_FORMAT';\nimport type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\nimport { CE_RECORD_KIND } from '#/databases/const-enum/CE_RECORD_KIND';\nimport type { IColumnRecord } from '#/databases/interfaces/IColumnRecord';\nimport type { IEntityRecord } from '#/databases/interfaces/IEntityRecord';\nimport type { IEntityWithColumnAndRelationAndIndex } from '#/databases/interfaces/IEntityWithColumnAndRelationAndIndex';\nimport type { IIndexRecord } from '#/databases/interfaces/IIndexRecord';\nimport type { IRecordMetadata } from '#/databases/interfaces/IRecordMetadata';\nimport type { IRelationRecord } from '#/databases/interfaces/IRelationRecord';\nimport type { IRenderData } from '#/databases/interfaces/IRenderData';\nimport type { TDatabaseRecord } from '#/databases/interfaces/TDatabaseRecord';\nimport { getSlashEndRoutePath } from '#/modules/getSlashEndRoutePath';\nimport alasql from 'alasql';\nimport { compareVersions } from 'compare-versions';\n\nexport async function getRenderData(\n records: TDatabaseRecord[],\n metadata: IRecordMetadata,\n option: Omit<IBuildCommandOption, 'config'>,\n): Promise<IRenderData> {\n const versionRows = (await alasql.promise('SELECT DISTINCT version FROM ?', [records])) as {\n version: string;\n }[];\n\n const unSortedVersions = versionRows.map((version) => version.version);\n const versions =\n option.versionFrom === 'timestamp'\n ? unSortedVersions.sort((l, r) => r.localeCompare(l))\n : unSortedVersions.sort((l, r) => compareVersions(r, l));\n\n const renderDatas = await Promise.all(\n versions.map(async (version) => {\n const entities = (await alasql.promise(`SELECT * FROM ? WHERE [$kind] = ? AND version = ?`, [\n records,\n 'entity',\n version,\n ])) as IEntityRecord[];\n\n const renderData: IEntityWithColumnAndRelationAndIndex[] = await Promise.all(\n entities.map(async (entity) => {\n const [columns, relations, indices] = await Promise.all([\n (await alasql.promise('SELECT * FROM ? WHERE [$kind] = ? AND entity = ? AND version = ?', [\n records,\n CE_RECORD_KIND.COLUMN,\n entity.entity,\n version,\n ])) as IColumnRecord[],\n\n (await alasql.promise('SELECT * FROM ? WHERE [$kind] = ? AND entity = ? AND version = ?', [\n records,\n CE_RECORD_KIND.RELATION,\n entity.entity,\n version,\n ])) as IRelationRecord[],\n\n (await alasql.promise('SELECT * FROM ? WHERE [$kind] = ? AND entity = ? AND version = ?', [\n records,\n CE_RECORD_KIND.INDEX,\n entity.entity,\n version,\n ])) as IIndexRecord[],\n ]);\n\n return { ...entity, columns, relations, indices } satisfies IEntityWithColumnAndRelationAndIndex;\n }),\n );\n\n return { version, entities: renderData, latest: version === metadata.version };\n }),\n );\n\n if (option.format === CE_OUTPUT_FORMAT.HTML) {\n return {\n versions: renderDatas,\n option: {\n ...option,\n routeBasePath: option.routeBasePath != null ? getSlashEndRoutePath(option.routeBasePath) : undefined,\n },\n metadata,\n };\n }\n\n return { versions: renderDatas, option, metadata };\n}\n", "import fs from 'fs';\nimport { parse } from 'jsonc-parser';\nimport { exists } from 'my-node-fp';\nimport type puppeteer from 'puppeteer';\n\nexport async function getPuppeteerConfig(confgFilePath?: string): Promise<Parameters<typeof puppeteer.launch>[0]> {\n try {\n if (confgFilePath == null) {\n return {};\n }\n\n if (await exists(confgFilePath)) {\n const buf = await fs.promises.readFile(confgFilePath);\n const option = parse(buf.toString()) as Parameters<typeof puppeteer.launch>[0];\n return option;\n }\n\n return {};\n } catch {\n return {};\n }\n}\n", "/* eslint-disable no-param-reassign, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any */\nimport type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\nimport type { getRenderData } from '#/creators/getRenderData';\nimport type { IErdiaDocument } from '#/creators/interfaces/IErdiaDocument';\nimport { betterMkdir } from '#/modules/files/betterMkdir';\nimport { getPuppeteerConfig } from '#/modules/getPuppeteerConfig';\nimport consola from 'consola';\nimport del from 'del';\nimport { isError } from 'my-easy-fp';\nimport fs from 'node:fs';\nimport pathe from 'pathe';\nimport * as puppeteer from 'puppeteer';\nimport type { AsyncReturnType } from 'type-fest';\n\nexport async function writeToImage(\n document: IErdiaDocument,\n option: Pick<\n IBuildCommandOption,\n 'output' | 'components' | 'prettierConfig' | 'viewportWidth' | 'viewportHeight' | 'backgroundColor' | 'imageFormat'\n >,\n renderData: AsyncReturnType<typeof getRenderData>,\n) {\n let localBrowser: puppeteer.Browser | undefined;\n let localPage: puppeteer.Page | undefined;\n\n try {\n const puppeteerConfig = await getPuppeteerConfig(option.prettierConfig);\n const browser = await puppeteer.launch({ ...puppeteerConfig, headless: true });\n const page = await browser.newPage();\n const puppeteerGotoOption: Parameters<typeof page.goto>[1] = {\n waitUntil: 'domcontentloaded',\n timeout: 60_000,\n };\n\n localBrowser = browser;\n localPage = page;\n\n await betterMkdir(document.filename);\n await fs.promises.writeFile(document.filename, document.content);\n await page.setViewport({ width: option.viewportWidth ?? 1280, height: option.viewportHeight ?? 720 * 2 });\n await page.goto(`file://${document.filename}`, puppeteerGotoOption);\n\n consola.debug(`file write start: ${document.filename}`);\n\n await page.$eval(\n 'body',\n (body, backgroundColor) => {\n body.style.background = backgroundColor;\n },\n option.backgroundColor ?? 'white',\n );\n\n if (option.imageFormat === 'svg') {\n // this source code from [mermaid-cli](https://github.com/mermaidjs/mermaid.cli/blob/46185413d75384cd7bceed802d187db6852f5190/index.js#L110)\n const svg = await page.$eval('#mermaid-diagram-container', (container) => container.innerHTML);\n\n if (svg == null) {\n await del(document.filename);\n throw new Error('invalid image html document template');\n }\n\n await fs.promises.writeFile(pathe.join(document.dirname, `${renderData.metadata.name}.svg`), svg);\n consola.debug('file write end');\n\n await del(document.filename);\n consola.info(`Component ER diagram successfully write on ${renderData.metadata.name}.svg`);\n\n return [pathe.join(document.dirname, `${renderData.metadata.name}.svg`)];\n }\n\n // this source code from [mermaid-cli](https://github.com/mermaidjs/mermaid.cli/blob/46185413d75384cd7bceed802d187db6852f5190/index.js#L113-L117)\n const clip = await page.$eval('svg', (htmlSvgElement: any) => {\n const react = htmlSvgElement.getBoundingClientRect();\n return { x: react.left, y: react.top, width: react.width, height: react.height };\n });\n\n await page.screenshot({\n path: pathe.join(document.dirname, `${renderData.metadata.name}.png`),\n clip,\n omitBackground: false,\n });\n\n consola.debug('file write end');\n\n await del(document.filename);\n consola.info(`Component ER diagram successfully write on ${renderData.metadata.name}.png`);\n\n return [pathe.join(document.dirname, `${renderData.metadata.name}.png`)];\n } catch (caught) {\n const err = isError(caught, new Error('unknown error raised from writeToImage'));\n\n consola.error(err.message);\n consola.error(err.stack);\n\n return false;\n } finally {\n consola.debug('Start page, brower close');\n\n if (localPage !== undefined && localPage !== null) {\n await localPage.close();\n }\n\n if (localBrowser !== undefined && localBrowser !== null) {\n await localBrowser.close();\n }\n }\n}\n", "import type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\nimport type { getRenderData } from '#/creators/getRenderData';\nimport type { IErdiaDocument } from '#/creators/interfaces/IErdiaDocument';\nimport { getPuppeteerConfig } from '#/modules/getPuppeteerConfig';\nimport consola from 'consola';\nimport del from 'del';\nimport fs from 'fs';\nimport { isError } from 'my-easy-fp';\nimport pathe from 'pathe';\nimport * as puppeteer from 'puppeteer';\nimport type { AsyncReturnType } from 'type-fest';\n\nexport async function writeToPdf(\n document: IErdiaDocument,\n option: Pick<\n IBuildCommandOption,\n | 'output'\n | 'components'\n | 'prettierConfig'\n | 'puppeteerConfig'\n | 'viewportWidth'\n | 'viewportHeight'\n | 'backgroundColor'\n >,\n renderData: AsyncReturnType<typeof getRenderData>,\n): Promise<string[]> {\n let localBrowser: puppeteer.Browser | undefined;\n let localPage: puppeteer.Page | undefined;\n\n try {\n const puppeteerConfig = await getPuppeteerConfig(option.puppeteerConfig);\n const browser = await puppeteer.launch({ ...puppeteerConfig, headless: true });\n const page = await browser.newPage();\n const puppeteerGotoOption: Parameters<typeof page.goto>[1] = {\n waitUntil: 'domcontentloaded',\n timeout: 60_000,\n };\n\n localBrowser = browser;\n localPage = page;\n\n consola.info('filename: ', document.filename);\n\n await page.setViewport({ width: option.viewportWidth ?? 1280, height: option.viewportHeight ?? 720 * 2 });\n await fs.promises.writeFile(document.filename, document.content);\n await page.goto(`file://${document.filename}`, puppeteerGotoOption);\n await page.pdf({\n path: pathe.join(document.dirname, `${renderData.metadata.name}.pdf`),\n printBackground: option.backgroundColor !== 'transparent',\n });\n\n await del(document.filename);\n\n return [pathe.join(document.dirname, `${renderData.metadata.name}.pdf`)];\n } catch (caught) {\n const err = isError(caught, new Error('unknown error raised from writeToPdf'));\n\n consola.error(err.message);\n consola.error(err.stack);\n\n return [];\n } finally {\n if (localPage !== undefined && localPage !== null) {\n consola.debug('Session Closed');\n await localPage.close();\n }\n\n if (localBrowser !== undefined && localBrowser !== null) {\n await localBrowser.close();\n }\n }\n}\n", "import type { IColumnRecord } from '#/databases/interfaces/IColumnRecord';\n\nexport function getColumnHash(column: Pick<IColumnRecord, 'entity' | 'dbName'>): string {\n const baseHash = [column.entity, column.dbName].join(':');\n const base64 = Buffer.from(baseHash).toString('base64');\n\n return base64;\n}\n", "import type { IEntityRecord } from '#/databases/interfaces/IEntityRecord';\n\nexport function getEntityHash(entity: Pick<IEntityRecord, 'entity' | 'dbName'>): string {\n const baseHash = [entity.entity, entity.dbName].join(':');\n const base64 = Buffer.from(baseHash).toString('base64');\n\n return base64;\n}\n", "import type { IIndexRecord } from '#/databases/interfaces/IIndexRecord';\n\nexport function getIndexHash(column: Pick<IIndexRecord, 'entity' | 'dbName'>): string {\n const baseHash = [column.entity, column.dbName].join(':');\n const base64 = Buffer.from(baseHash).toString('base64');\n\n return base64;\n}\n", "import type { IRelationRecord } from '#/databases/interfaces/IRelationRecord';\n\nexport function getPlainRelationType(relationType: IRelationRecord['relationType']) {\n if (relationType === 'one-to-one') {\n return 'one-to-one';\n }\n\n if (relationType === 'many-to-many') {\n return 'many-to-many';\n }\n\n return 'one-to-many';\n}\n", "import { getPlainRelationType } from '#/common/getPlainRelationType';\nimport type { IRelationRecord } from '#/databases/interfaces/IRelationRecord';\n\nexport function getRelationHash(\n relation: Pick<IRelationRecord, 'entity' | 'inverseEntityName' | 'relationType'>,\n): string {\n const entities = [relation.entity, relation.inverseEntityName].sort((l, r) => l.localeCompare(r));\n\n const plainRelationType = getPlainRelationType(relation.relationType);\n const baseHash = [...entities, plainRelationType].join(':');\n const base64 = Buffer.from(baseHash).toString('base64');\n\n return base64;\n}\n", "export const CE_CHANGE_KIND = {\n CHANGE: 'change',\n ADD: 'add',\n DELETE: 'delete',\n NONE: 'none',\n} as const;\n\nexport type CE_CHANGE_KIND = (typeof CE_CHANGE_KIND)[keyof typeof CE_CHANGE_KIND];\n", "import { getColumnHash } from '#/common/getColumnHash';\nimport { getEntityHash } from '#/common/getEntityHash';\nimport { getIndexHash } from '#/common/getIndexHash';\nimport { getRelationHash } from '#/common/getRelationHash';\nimport { CE_CHANGE_KIND } from '#/databases/const-enum/CE_CHANGE_KIND';\nimport { CE_RECORD_KIND } from '#/databases/const-enum/CE_RECORD_KIND';\nimport type { IRecordMetadata } from '#/databases/interfaces/IRecordMetadata';\nimport type { TDatabaseRecord } from '#/databases/interfaces/TDatabaseRecord';\nimport { detailedDiff } from 'deep-object-diff';\nimport { settify } from 'my-easy-fp';\n\nexport function compareDatabase(\n metadata: IRecordMetadata,\n next: TDatabaseRecord[],\n prev: TDatabaseRecord[],\n): TDatabaseRecord[] {\n if (prev.length <= 0) {\n return next.map((record) => ({ ...record, change: CE_CHANGE_KIND.NONE }));\n }\n\n const nextMap = next.reduce<Record<string, TDatabaseRecord>>((aggregation, record) => {\n switch (record.$kind) {\n case CE_RECORD_KIND.ENTITY:\n return { ...aggregation, [getEntityHash(record)]: record };\n case CE_RECORD_KIND.COLUMN:\n return { ...aggregation, [getColumnHash(record)]: record };\n case CE_RECORD_KIND.RELATION:\n return { ...aggregation, [getRelationHash(record)]: record };\n case CE_RECORD_KIND.INDEX:\n return { ...aggregation, [getIndexHash(record)]: record };\n default:\n return aggregation;\n }\n }, {});\n\n const prevMap = prev.reduce<Record<string, TDatabaseRecord>>((aggregation, record) => {\n switch (record.$kind) {\n case CE_RECORD_KIND.ENTITY:\n return { ...aggregation, [getEntityHash(record)]: record };\n case CE_RECORD_KIND.COLUMN:\n return { ...aggregation, [getColumnHash(record)]: record };\n case CE_RECORD_KIND.RELATION:\n return { ...aggregation, [getRelationHash(record)]: record };\n case CE_RECORD_KIND.INDEX:\n return { ...aggregation, [getIndexHash(record)]: record };\n default:\n return aggregation;\n }\n }, {});\n\n const compared = settify([...Object.keys(nextMap), ...Object.keys(prevMap)]).map((key): TDatabaseRecord => {\n const fromNext = nextMap[key];\n const fromPrev = prevMap[key];\n // add\n if (fromNext != null && fromPrev == null) {\n return { ...fromNext, change: CE_CHANGE_KIND.ADD } satisfies TDatabaseRecord;\n }\n\n // delete\n if (fromNext == null && fromPrev != null) {\n return { ...fromPrev, change: CE_CHANGE_KIND.DELETE, version: metadata.version } satisfies TDatabaseRecord;\n }\n\n const forCompareNext: TDatabaseRecord = {\n ...fromNext,\n title: fromNext.title ?? '',\n change: CE_CHANGE_KIND.NONE,\n createdAt: '',\n updatedAt: '',\n version: '',\n };\n const forComparePrev: TDatabaseRecord = {\n ...fromPrev,\n title: fromPrev.title ?? '',\n change: CE_CHANGE_KIND.NONE,\n createdAt: '',\n updatedAt: '',\n version: '',\n };\n const diffed = detailedDiff(forCompareNext, forComparePrev);\n\n // none\n if (\n Object.keys(diffed.added).length <= 0 &&\n Object.keys(diffed.updated).length <= 0 &&\n Object.keys(diffed.deleted).length <= 0\n ) {\n return { ...fromNext, change: CE_CHANGE_KIND.NONE } satisfies TDatabaseRecord;\n }\n\n // change\n return { ...fromNext, change: CE_CHANGE_KIND.CHANGE, prev: fromPrev } satisfies TDatabaseRecord;\n });\n\n return compared;\n}\n", "import { CE_DEFAULT_VALUE } from '#/configs/const-enum/CE_DEFAULT_VALUE';\nimport type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\nimport type { TDatabaseRecord } from '#/databases/interfaces/TDatabaseRecord';\nimport { getOutputDirPath } from '#/modules/files/getOutputDirPath';\nimport fs from 'node:fs';\nimport pathe from 'pathe';\n\nexport async function flushDatabase(\n option: Pick<IBuildCommandOption, 'databasePath'>,\n records: TDatabaseRecord[],\n): Promise<TDatabaseRecord[]> {\n const dirname = await getOutputDirPath({ output: option.databasePath }, process.cwd());\n const filename = pathe.join(dirname, CE_DEFAULT_VALUE.DATABASE_FILENAME);\n\n if (filename == null) {\n throw new Error(`invalid database name: undefined`);\n }\n\n await fs.promises.writeFile(\n pathe.join(dirname, CE_DEFAULT_VALUE.DATABASE_FILENAME),\n JSON.stringify(records, undefined, 2),\n );\n\n return records;\n}\n", "import { CE_DEFAULT_VALUE } from '#/configs/const-enum/CE_DEFAULT_VALUE';\nimport type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\nimport type { TDatabaseRecord } from '#/databases/interfaces/TDatabaseRecord';\nimport { getOutputDirPath } from '#/modules/files/getOutputDirPath';\nimport { parse } from 'jsonc-parser';\nimport { isFalse } from 'my-easy-fp';\nimport { exists } from 'my-node-fp';\nimport fs from 'node:fs';\nimport pathe from 'pathe';\n\nexport async function openDatabase(option: Pick<IBuildCommandOption, 'databasePath'>): Promise<TDatabaseRecord[]> {\n const dirname = await getOutputDirPath({ output: option.databasePath }, process.cwd());\n const filename = pathe.join(dirname, CE_DEFAULT_VALUE.DATABASE_FILENAME);\n\n if (filename == null) {\n return [];\n }\n\n if (isFalse(await exists(filename))) {\n return [];\n }\n\n const db = parse((await fs.promises.readFile(filename)).toString()) as TDatabaseRecord[];\n return db;\n}\n", "import type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption';\nimport type { IRecordMetadata } from '#/databases/interfaces/IRecordMetadata';\nimport type { TDatabaseRecord } from '#/databases/interfaces/TDatabaseRecord';\nimport alasql from 'alasql';\nimport { compareVersions } from 'compare-versions';\nimport { atOrThrow } from 'my-easy-fp';\n\nexport async function processDatabase(\n metadata: IRecordMetadata,\n db: TDatabaseRecord[],\n option: Pick<IBuildCommandOption, 'versionFrom'>,\n): Promise<{ next: TDatabaseRecord[]; deleted: TDatabaseRecord[]; prev: TDatabaseRecord[] }> {\n // case 01. empty database, first create database\n if (db.length <= 0) {\n return { next: [], deleted: [], prev: [] };\n }\n\n const currentVersion = metadata.version;\n const versions = (await alasql.promise('SELECT DISTINCT version FROM ?', [db])) as {\n version: string;\n }[];\n\n const sortedVersions =\n option.versionFrom === 'timestamp'\n ? versions.sort((l, r) => r.versi