diff --git a/.eslintrc.cjs b/.eslintrc.cjs index cdd29b2..f6a9d0b 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -124,6 +124,16 @@ module.exports = { 'no-console': ['off'], }, }, + { + files: ['src/modules/loggers/Logger.ts'], + rules: { + 'class-methods-use-this': ['off'], + '@typescript-eslint/consistent-type-imports': ['off'], + '@typescript-eslint/no-redundant-type-constituents': ['off'], + '@typescript-eslint/no-unsafe-argument': ['off'], + '@typescript-eslint/no-explicit-any': ['off'], + }, + }, { files: ['vitest.config.ts'], rules: { diff --git a/src/cli.ts b/src/cli.ts index fc19fdd..5f72381 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -11,6 +11,7 @@ import type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOpti import type { ICommonOption } from '#/configs/interfaces/ICommonOption'; import { preLoadConfig } from '#/configs/modules/preLoadConfig'; import '#/modules/containers/container'; +import { createLogger } from '#/modules/loggers/createLogger'; import consola from 'consola'; import { isError } from 'my-easy-fp'; import sourceMapSupport from 'source-map-support'; @@ -18,6 +19,7 @@ import yargs, { type CommandModule } from 'yargs'; import { hideBin } from 'yargs/helpers'; sourceMapSupport.install(); +createLogger(); const buildCmdModule: CommandModule = { command: CE_COMMAND_LIST.BUILD, diff --git a/src/cli/commands/buildDocumentCommandHandler.ts b/src/cli/commands/buildDocumentCommandHandler.ts index 9a971c2..d9593dc 100644 --- a/src/cli/commands/buildDocumentCommandHandler.ts +++ b/src/cli/commands/buildDocumentCommandHandler.ts @@ -1,174 +1,26 @@ -import { getDatabaseName } from '#/common/getDatabaseName'; -import { getMetadata } from '#/common/getMetadata'; -import { CE_MERMAID_THEME } from '#/configs/const-enum/CE_MERMAID_THEME'; -import { CE_OUTPUT_FORMAT } from '#/configs/const-enum/CE_OUTPUT_FORMAT'; import type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption'; -import { createHtml } from '#/creators/createHtml'; -import { createImageHtml } from '#/creators/createImageHtml'; -import { createMarkdown } from '#/creators/createMarkdown'; -import { createPdfHtml } from '#/creators/createPdfHtml'; -import { getRenderData } from '#/creators/getRenderData'; -import type { IReason } from '#/creators/interfaces/IReason'; -import { writeToImage } from '#/creators/writeToImage'; -import { writeToPdf } from '#/creators/writeToPdf'; -import { compareDatabase } from '#/databases/compareDatabase'; -import { flushDatabase } from '#/databases/flushDatabase'; -import type { IRelationRecord } from '#/databases/interfaces/IRelationRecord'; -import { openDatabase } from '#/databases/openDatabase'; -import { processDatabase } from '#/databases/processDatabase'; +import { building } from '#/modules/commands/building'; import { container } from '#/modules/containers/container'; -import { SymbolDataSource } from '#/modules/containers/keys/SymbolDataSource'; -import { SymbolDefaultTemplate } from '#/modules/containers/keys/SymbolDefaultTemplate'; -import { SymbolTemplate } from '#/modules/containers/keys/SymbolTemplate'; -import { SymbolTemplateRenderer } from '#/modules/containers/keys/SymbolTemplateRenderer'; -import { betterMkdir } from '#/modules/files/betterMkdir'; -import { TemplateRenderer } from '#/templates/TemplateRenderer'; -import { loadTemplates } from '#/templates/modules/loadTemplates'; -import { getColumnRecord } from '#/typeorm/columns/getColumnRecord'; -import { getEntityRecords } from '#/typeorm/entities/getEntityRecords'; -import { getDataSource } from '#/typeorm/getDataSource'; -import { getIndexRecords } from '#/typeorm/indices/getIndexRecords'; -import { dedupeManaToManyRelationRecord } from '#/typeorm/relations/dedupeManaToManyRelationRecord'; -import { getRelationRecords } from '#/typeorm/relations/getRelationRecords'; +import { SymbolLogger } from '#/modules/containers/keys/SymbolLogger'; +import type { Logger } from '#/modules/loggers/Logger'; import { showLogo } from '@maeum/cli-logo'; -import { asValue } from 'awilix'; -import chalk from 'chalk'; -import consola from 'consola'; -import fastSafeStringify from 'fast-safe-stringify'; -import { isError, isFalse } from 'my-easy-fp'; -import { isFail, isPass, type IFail, type IPass } from 'my-only-either'; -import fs from 'node:fs'; -import type { DataSource } from 'typeorm'; +import { LogLevels } from 'consola'; export async function buildDocumentCommandHandler(option: IBuildCommandOption) { - try { - if (option.showLogo != null) { - await showLogo({ - message: 'erdia', - figlet: { font: 'ANSI Shadow', width: 80 }, - color: 'cyan', - }); - } else { - consola.info('erdia build start'); - } - - consola.info(`connection initialize: "${chalk.yellowBright(`${option.dataSourcePath}`)}"`); - - const dataSource = await getDataSource(option); - const [templates] = await Promise.all([await loadTemplates(option), await dataSource.initialize()]); - const renderer = new TemplateRenderer(templates.template, templates.default); - - if (isFalse(dataSource.isInitialized)) { - throw new Error(`Cannot initialize in ${fastSafeStringify(dataSource.options, undefined, 2)}`); - } - - container.register(SymbolDefaultTemplate, asValue(templates.default)); - container.register(SymbolTemplate, asValue(templates.template)); - container.register(SymbolDataSource, asValue(dataSource)); - container.register(SymbolTemplateRenderer, asValue(renderer)); - - const metadata = await getMetadata(option); - - consola.success('connection initialized'); - consola.info(`version: ${metadata.version}`); - - consola.info(`extract entities in ${getDatabaseName(dataSource.options)}`); - - const entities = getEntityRecords(dataSource, metadata); - const indicesRecords = getIndexRecords(dataSource, metadata); - const columns = dataSource.entityMetadatas - .map((entity) => entity.columns.map((column) => getColumnRecord(column, option, metadata, indicesRecords))) - .flat(); - - const relationRecords = getRelationRecords(dataSource, metadata); - - const failRelations = relationRecords - .filter((relationRecord): relationRecord is IFail => isFail(relationRecord)) - .map((relationRecord) => relationRecord.fail) - .flat(); - - failRelations.forEach((relation) => consola.warn(relation.message)); - - const passRelations = relationRecords - .filter((relation): relation is IPass => isPass(relation)) - .map((relationRecord) => relationRecord.pass) - .flat(); - - const dedupedRelations = dedupeManaToManyRelationRecord(passRelations); - const records = [...entities, ...columns, ...dedupedRelations, ...indicesRecords]; - - consola.success('complete extraction'); - consola.info('Database open and processing'); - - const db = await openDatabase(option); - const processedDb = await processDatabase(metadata, db, option); - const compared = compareDatabase(metadata, records, processedDb.prev); - - const nextDb = [...compared, ...processedDb.next]; - const renderData = await getRenderData(nextDb, metadata, option); - - await flushDatabase(option, nextDb); - consola.success('Database open and processing completed'); - - consola.info(`output format: ${option.format}`); - - if (option.format === CE_OUTPUT_FORMAT.HTML) { - const imageOption: IBuildCommandOption = { - ...option, - format: CE_OUTPUT_FORMAT.IMAGE, - imageFormat: 'svg', - width: '200%', - theme: CE_MERMAID_THEME.DARK, - }; - - const documents = await createHtml(option, renderData); - await Promise.all( - documents.map(async (document) => { - await betterMkdir(document.dirname); - await fs.promises.writeFile(document.filename, document.content); - }), - ); - - if (!option.skipImageInHtml) { - const imageDocument = await createImageHtml(imageOption, renderData); - await writeToImage(imageDocument, imageOption, renderData); - } - - return documents.map((document) => document.filename); - } - - if (option.format === CE_OUTPUT_FORMAT.MARKDOWN) { - const document = await createMarkdown(option, renderData); - await betterMkdir(document.dirname); - await fs.promises.writeFile(document.filename, document.content); - return [document.filename]; - } - - if (option.format === CE_OUTPUT_FORMAT.PDF) { - const document = await createPdfHtml(option, renderData); - const filenames = await writeToPdf(document, option, renderData); - return filenames; - } - - if (option.format === CE_OUTPUT_FORMAT.IMAGE) { - const document = await createImageHtml(option, renderData); - await betterMkdir(document.dirname); - const filenames = await writeToImage(document, option, renderData); - return filenames; - } - - return []; - } catch (caught) { - const err = isError(caught, new Error('unknown error raised from createHtmlDocCommand')); - - consola.error(err.message); - consola.error(err.stack); - - return []; - } finally { - const dataSource = container.resolve(SymbolDataSource); - if (dataSource != null) { - await dataSource.destroy(); - } + const logger = container.resolve(SymbolLogger); + + logger.level = LogLevels.info; + logger.enable = true; + + if (option.showLogo != null) { + await showLogo({ + message: 'erdia', + figlet: { font: 'ANSI Shadow', width: 80 }, + color: 'cyan', + }); + } else { + logger.info('erdia build start'); } + + await building(option); } diff --git a/src/cli/commands/cleanDocumentCommandHandler.ts b/src/cli/commands/cleanDocumentCommandHandler.ts index 897288c..0b6f237 100644 --- a/src/cli/commands/cleanDocumentCommandHandler.ts +++ b/src/cli/commands/cleanDocumentCommandHandler.ts @@ -1,67 +1,26 @@ -import { getMetadata } from '#/common/getMetadata'; -import { CE_DEFAULT_VALUE } from '#/configs/const-enum/CE_DEFAULT_VALUE'; import type { ICommonOption } from '#/configs/interfaces/ICommonOption'; -import { getCwd } from '#/configs/modules/getCwd'; +import { cleaning } from '#/modules/commands/cleaning'; import { container } from '#/modules/containers/container'; -import { SymbolDataSource } from '#/modules/containers/keys/SymbolDataSource'; -import { getOutputDirectory } from '#/modules/files/getOutputDirectory'; -import { getDataSource } from '#/typeorm/getDataSource'; +import { SymbolLogger } from '#/modules/containers/keys/SymbolLogger'; +import type { Logger } from '#/modules/loggers/Logger'; import { showLogo } from '@maeum/cli-logo'; -import { asValue } from 'awilix'; -import consola from 'consola'; -import del from 'del'; -import fastSafeStringify from 'fast-safe-stringify'; -import { isError, isFalse } from 'my-easy-fp'; -import pathe from 'pathe'; -import type { DataSource } from 'typeorm'; +import consola, { LogLevels } from 'consola'; export async function cleanDocumentCommandHandler(option: ICommonOption) { - let localDataSource: DataSource | undefined; - - try { - if (option.showLogo != null) { - await showLogo({ - message: 'erdia', - figlet: { font: 'ANSI Shadow', width: 80 }, - color: 'cyan', - }); - } else { - consola.info('erdia build start'); - } - - const dataSource = await getDataSource(option); - await dataSource.initialize(); - - if (isFalse(dataSource.isInitialized)) { - throw new Error(`Cannot initialize in ${fastSafeStringify(dataSource.options, undefined, 2)}`); - } - - container.register(SymbolDataSource, asValue(dataSource)); - - const metadata = await getMetadata({ ...option, versionFrom: 'package.json', projectName: 'app' }); - const outputDirPath = await getOutputDirectory(option, getCwd(process.env)); - - const filenames = [ - pathe.join(outputDirPath, CE_DEFAULT_VALUE.HTML_MERMAID_FILENAME), - pathe.join(outputDirPath, CE_DEFAULT_VALUE.HTML_INDEX_FILENAME), - pathe.join(outputDirPath, `${metadata.name}.md`), - pathe.join(outputDirPath, `${metadata.name}.png`), - pathe.join(outputDirPath, `${metadata.name}.svg`), - ]; - - await del(filenames); - - return filenames; - } catch (caught) { - const err = isError(caught, new Error('unknown error raised from createHtmlDocCommand')); - - consola.error(err.message); - consola.error(err.stack); - - return []; - } finally { - if (localDataSource != null) { - await localDataSource.destroy(); - } + const logger = container.resolve(SymbolLogger); + + logger.level = LogLevels.info; + logger.enable = true; + + if (option.showLogo != null) { + await showLogo({ + message: 'erdia', + figlet: { font: 'ANSI Shadow', width: 80 }, + color: 'cyan', + }); + } else { + consola.info('erdia build start'); } + + await cleaning(option); } diff --git a/src/cli/commands/initConfigCommandHandler.ts b/src/cli/commands/initConfigCommandHandler.ts index a456538..543bf1e 100644 --- a/src/cli/commands/initConfigCommandHandler.ts +++ b/src/cli/commands/initConfigCommandHandler.ts @@ -1,25 +1,15 @@ -import { CE_DEFAULT_VALUE } from '#/configs/const-enum/CE_DEFAULT_VALUE'; -import { getConfigContent } from '#/configs/modules/getConfigContent'; -import { applyPrettier } from '#/creators/applyPretter'; +import { initializing } from '#/modules/commands/initializing'; import { container } from '#/modules/containers/container'; -import { SymbolTemplateRenderer } from '#/modules/containers/keys/SymbolTemplateRenderer'; -import { TemplateRenderer } from '#/templates/TemplateRenderer'; -import { loadTemplates } from '#/templates/modules/loadTemplates'; -import { asValue } from 'awilix'; -import consola from 'consola'; -import fs from 'fs'; +import { SymbolLogger } from '#/modules/containers/keys/SymbolLogger'; +import type { Logger } from '#/modules/loggers/Logger'; +import { LogLevels } from 'consola'; export async function initConfigCommandHandler() { - const templates = await loadTemplates(); - const renderer = new TemplateRenderer(templates.template, templates.default); + const logger = container.resolve(SymbolLogger); - container.register(SymbolTemplateRenderer, asValue(renderer)); + logger.level = LogLevels.info; + logger.enable = true; - const rawConfig = await getConfigContent(); - const prettiered = await applyPrettier(rawConfig, 'json'); - - await fs.promises.writeFile(CE_DEFAULT_VALUE.CONFIG_FILE_NAME, prettiered); - consola.info(`${CE_DEFAULT_VALUE.CONFIG_FILE_NAME} file created`); - - return rawConfig; + const configFilePath = await initializing(); + return configFilePath; } diff --git a/src/cli/commands/templateEjectCommandHandler.ts b/src/cli/commands/templateEjectCommandHandler.ts index bc94715..efd5ab2 100644 --- a/src/cli/commands/templateEjectCommandHandler.ts +++ b/src/cli/commands/templateEjectCommandHandler.ts @@ -1,19 +1,17 @@ -import { CE_DEFAULT_VALUE } from '#/configs/const-enum/CE_DEFAULT_VALUE'; import type { ICommonOption } from '#/configs/interfaces/ICommonOption'; -import { getCwd } from '#/configs/modules/getCwd'; -import { betterMkdir } from '#/modules/files/betterMkdir'; -import { getGlobFiles } from '#/modules/files/getGlobFiles'; -import { getOutputDirectory } from '#/modules/files/getOutputDirectory'; -import { defaultExclude } from '#/modules/scopes/defaultExclude'; -import { getTemplatePath } from '#/templates/modules/getTemplatePath'; +import { ejecting } from '#/modules/commands/ejecting'; +import { container } from '#/modules/containers/container'; +import { SymbolLogger } from '#/modules/containers/keys/SymbolLogger'; +import type { Logger } from '#/modules/loggers/Logger'; import { showLogo } from '@maeum/cli-logo'; -import consola from 'consola'; -import fs from 'fs'; -import { Glob } from 'glob'; -import { getDirname, startSepRemove } from 'my-node-fp'; -import pathe from 'pathe'; +import consola, { LogLevels } from 'consola'; export async function templateEjectCommandHandler(option: Pick) { + const logger = container.resolve(SymbolLogger); + + logger.level = LogLevels.info; + logger.enable = true; + if (option.showLogo != null) { await showLogo({ message: 'erdia', @@ -24,38 +22,6 @@ export async function templateEjectCommandHandler(option: Pick { - const subDirPath = await getDirname(originTemplateFilePath); - const subFilePath = startSepRemove(originTemplateFilePath.replace(subDirPath, '')); - const targetTemplateSubDirPath = pathe.join( - targetTemplateDirPath, - startSepRemove(subDirPath.replace(originTemplateDirPath, '')), - ); - - await betterMkdir(targetTemplateSubDirPath); - - const templateFileBuf = await fs.promises.readFile(originTemplateFilePath); - await fs.promises.writeFile(pathe.join(targetTemplateSubDirPath, subFilePath), templateFileBuf); - }), - ); - - consola.success('eject success: ', targetTemplateDirPath); - + const targetTemplateDirPath = await ejecting(option); return targetTemplateDirPath; } diff --git a/src/configs/modules/getConfigContent.ts b/src/configs/modules/getConfigContent.ts index dde40c4..3e2fb11 100644 --- a/src/configs/modules/getConfigContent.ts +++ b/src/configs/modules/getConfigContent.ts @@ -1,4 +1,3 @@ -import { templateEjectCommandHandler } from '#/cli/commands/templateEjectCommandHandler'; import { CE_DEFAULT_VALUE } from '#/configs/const-enum/CE_DEFAULT_VALUE'; import { CE_ENTITY_VERSION_FROM } from '#/configs/const-enum/CE_ENTITY_VERSION_FROM'; import { CE_IMAGE_FORMAT } from '#/configs/const-enum/CE_IMAGE_FORMAT'; @@ -8,6 +7,7 @@ import { CE_OUTPUT_FORMAT } from '#/configs/const-enum/CE_OUTPUT_FORMAT'; import type { IInitDocAnswer } from '#/configs/interfaces/InquirerAnswer'; import { getAutoCompleteSource } from '#/configs/modules/getAutoCompleteSource'; import { getCwd } from '#/configs/modules/getCwd'; +import { ejecting } from '#/modules/commands/ejecting'; import { container } from '#/modules/containers/container'; import { SymbolTemplateRenderer } from '#/modules/containers/keys/SymbolTemplateRenderer'; import { getGlobFiles } from '#/modules/files/getGlobFiles'; @@ -214,7 +214,7 @@ export async function getConfigContent() { ]); const templateDir = await (answer.isEjectTemplate - ? templateEjectCommandHandler({ output: getCwd(process.env), showLogo: false }) + ? ejecting({ output: getCwd(process.env), showLogo: false }) : Promise.resolve(undefined)); const renderer = container.resolve(SymbolTemplateRenderer); const file = await renderer.evaluate(CE_TEMPLATE_NAME.CONFIG_JSON, { diff --git a/src/modules/commands/building.ts b/src/modules/commands/building.ts new file mode 100644 index 0000000..7ff018c --- /dev/null +++ b/src/modules/commands/building.ts @@ -0,0 +1,166 @@ +import { getDatabaseName } from '#/common/getDatabaseName'; +import { getMetadata } from '#/common/getMetadata'; +import { CE_MERMAID_THEME } from '#/configs/const-enum/CE_MERMAID_THEME'; +import { CE_OUTPUT_FORMAT } from '#/configs/const-enum/CE_OUTPUT_FORMAT'; +import type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption'; +import { createHtml } from '#/creators/createHtml'; +import { createImageHtml } from '#/creators/createImageHtml'; +import { createMarkdown } from '#/creators/createMarkdown'; +import { createPdfHtml } from '#/creators/createPdfHtml'; +import { getRenderData } from '#/creators/getRenderData'; +import type { IReason } from '#/creators/interfaces/IReason'; +import { writeToImage } from '#/creators/writeToImage'; +import { writeToPdf } from '#/creators/writeToPdf'; +import { compareDatabase } from '#/databases/compareDatabase'; +import { flushDatabase } from '#/databases/flushDatabase'; +import type { IRelationRecord } from '#/databases/interfaces/IRelationRecord'; +import { openDatabase } from '#/databases/openDatabase'; +import { processDatabase } from '#/databases/processDatabase'; +import { container } from '#/modules/containers/container'; +import { SymbolDataSource } from '#/modules/containers/keys/SymbolDataSource'; +import { SymbolDefaultTemplate } from '#/modules/containers/keys/SymbolDefaultTemplate'; +import { SymbolLogger } from '#/modules/containers/keys/SymbolLogger'; +import { SymbolTemplate } from '#/modules/containers/keys/SymbolTemplate'; +import { SymbolTemplateRenderer } from '#/modules/containers/keys/SymbolTemplateRenderer'; +import { betterMkdir } from '#/modules/files/betterMkdir'; +import type { Logger } from '#/modules/loggers/Logger'; +import { createLogger } from '#/modules/loggers/createLogger'; +import { TemplateRenderer } from '#/templates/TemplateRenderer'; +import { loadTemplates } from '#/templates/modules/loadTemplates'; +import { getColumnRecord } from '#/typeorm/columns/getColumnRecord'; +import { getEntityRecords } from '#/typeorm/entities/getEntityRecords'; +import { getDataSource } from '#/typeorm/getDataSource'; +import { getIndexRecords } from '#/typeorm/indices/getIndexRecords'; +import { dedupeManaToManyRelationRecord } from '#/typeorm/relations/dedupeManaToManyRelationRecord'; +import { getRelationRecords } from '#/typeorm/relations/getRelationRecords'; +import { asValue } from 'awilix'; +import chalk from 'chalk'; +import fastSafeStringify from 'fast-safe-stringify'; +import { isError, isFalse } from 'my-easy-fp'; +import { isFail, isPass, type IFail, type IPass } from 'my-only-either'; +import fs from 'node:fs'; +import type { DataSource } from 'typeorm'; + +export async function building(option: IBuildCommandOption) { + createLogger(); + const logger = container.resolve(SymbolLogger); + + try { + logger.info(`connection initialize: "${chalk.yellowBright(`${option.dataSourcePath}`)}"`); + + const dataSource = await getDataSource(option); + const [templates] = await Promise.all([await loadTemplates(option), await dataSource.initialize()]); + const renderer = new TemplateRenderer(templates.template, templates.default); + + if (isFalse(dataSource.isInitialized)) { + throw new Error(`Cannot initialize in ${fastSafeStringify(dataSource.options, undefined, 2)}`); + } + + container.register(SymbolDefaultTemplate, asValue(templates.default)); + container.register(SymbolTemplate, asValue(templates.template)); + container.register(SymbolDataSource, asValue(dataSource)); + container.register(SymbolTemplateRenderer, asValue(renderer)); + + const metadata = await getMetadata(option); + + logger.success('connection initialized'); + logger.info(`version: ${metadata.version}`); + + logger.info(`extract entities in ${getDatabaseName(dataSource.options)}`); + + const entities = getEntityRecords(dataSource, metadata); + const indicesRecords = getIndexRecords(dataSource, metadata); + const columns = dataSource.entityMetadatas + .map((entity) => entity.columns.map((column) => getColumnRecord(column, option, metadata, indicesRecords))) + .flat(); + + const relationRecords = getRelationRecords(dataSource, metadata); + + const failRelations = relationRecords + .filter((relationRecord): relationRecord is IFail => isFail(relationRecord)) + .map((relationRecord) => relationRecord.fail) + .flat(); + + failRelations.forEach((relation) => logger.warn(relation.message)); + + const passRelations = relationRecords + .filter((relation): relation is IPass => isPass(relation)) + .map((relationRecord) => relationRecord.pass) + .flat(); + + const dedupedRelations = dedupeManaToManyRelationRecord(passRelations); + const records = [...entities, ...columns, ...dedupedRelations, ...indicesRecords]; + + logger.success('complete extraction'); + logger.info('Database open and processing'); + + const db = await openDatabase(option); + const processedDb = await processDatabase(metadata, db, option); + const compared = compareDatabase(metadata, records, processedDb.prev); + + const nextDb = [...compared, ...processedDb.next]; + const renderData = await getRenderData(nextDb, metadata, option); + + await flushDatabase(option, nextDb); + logger.success('Database open and processing completed'); + + logger.info(`output format: ${option.format}`); + + if (option.format === CE_OUTPUT_FORMAT.HTML) { + const imageOption: IBuildCommandOption = { + ...option, + format: CE_OUTPUT_FORMAT.IMAGE, + imageFormat: 'svg', + width: '200%', + theme: CE_MERMAID_THEME.DARK, + }; + + const documents = await createHtml(option, renderData); + await Promise.all( + documents.map(async (document) => { + await betterMkdir(document.dirname); + await fs.promises.writeFile(document.filename, document.content); + }), + ); + + if (!option.skipImageInHtml) { + const imageDocument = await createImageHtml(imageOption, renderData); + await writeToImage(imageDocument, imageOption, renderData); + } + + return documents.map((document) => document.filename); + } + + if (option.format === CE_OUTPUT_FORMAT.MARKDOWN) { + const document = await createMarkdown(option, renderData); + await betterMkdir(document.dirname); + await fs.promises.writeFile(document.filename, document.content); + return [document.filename]; + } + + if (option.format === CE_OUTPUT_FORMAT.PDF) { + const document = await createPdfHtml(option, renderData); + const filenames = await writeToPdf(document, option, renderData); + return filenames; + } + + if (option.format === CE_OUTPUT_FORMAT.IMAGE) { + const document = await createImageHtml(option, renderData); + await betterMkdir(document.dirname); + const filenames = await writeToImage(document, option, renderData); + return filenames; + } + + return []; + } catch (caught) { + const err = isError(caught, new Error('unknown error raised from createHtmlDocCommand')); + logger.error(err); + + return []; + } finally { + if (container.hasRegistration(SymbolDataSource)) { + const dataSource = container.resolve(SymbolDataSource); + await dataSource.destroy(); + } + } +} diff --git a/src/modules/commands/cleaning.ts b/src/modules/commands/cleaning.ts new file mode 100644 index 0000000..7c068b8 --- /dev/null +++ b/src/modules/commands/cleaning.ts @@ -0,0 +1,64 @@ +import { getMetadata } from '#/common/getMetadata'; +import { CE_DEFAULT_VALUE } from '#/configs/const-enum/CE_DEFAULT_VALUE'; +import type { ICommonOption } from '#/configs/interfaces/ICommonOption'; +import { getCwd } from '#/configs/modules/getCwd'; +import { container } from '#/modules/containers/container'; +import { SymbolDataSource } from '#/modules/containers/keys/SymbolDataSource'; +import { getOutputDirectory } from '#/modules/files/getOutputDirectory'; +import { getDataSource } from '#/typeorm/getDataSource'; +import { showLogo } from '@maeum/cli-logo'; +import { asValue } from 'awilix'; +import consola from 'consola'; +import del from 'del'; +import fastSafeStringify from 'fast-safe-stringify'; +import { isError, isFalse } from 'my-easy-fp'; +import pathe from 'pathe'; +import type { DataSource } from 'typeorm'; + +export async function cleaning(option: ICommonOption) { + try { + if (option.showLogo != null) { + await showLogo({ + message: 'erdia', + figlet: { font: 'ANSI Shadow', width: 80 }, + color: 'cyan', + }); + } else { + consola.info('erdia build start'); + } + + const dataSource = await getDataSource(option); + await dataSource.initialize(); + + if (isFalse(dataSource.isInitialized)) { + throw new Error(`Cannot initialize in ${fastSafeStringify(dataSource.options, undefined, 2)}`); + } + + container.register(SymbolDataSource, asValue(dataSource)); + + const metadata = await getMetadata({ ...option, versionFrom: 'package.json', projectName: 'app' }); + const outputDirPath = await getOutputDirectory(option, getCwd(process.env)); + + const filenames = [ + pathe.join(outputDirPath, CE_DEFAULT_VALUE.HTML_MERMAID_FILENAME), + pathe.join(outputDirPath, CE_DEFAULT_VALUE.HTML_INDEX_FILENAME), + pathe.join(outputDirPath, `${metadata.name}.md`), + pathe.join(outputDirPath, `${metadata.name}.png`), + pathe.join(outputDirPath, `${metadata.name}.svg`), + ]; + + await del(filenames); + + return filenames; + } catch (caught) { + const err = isError(caught, new Error('unknown error raised from createHtmlDocCommand')); + consola.error(err); + + return []; + } finally { + if (container.hasRegistration(SymbolDataSource)) { + const dataSource = container.resolve(SymbolDataSource); + await dataSource.destroy(); + } + } +} diff --git a/src/modules/commands/ejecting.ts b/src/modules/commands/ejecting.ts new file mode 100644 index 0000000..ac20d92 --- /dev/null +++ b/src/modules/commands/ejecting.ts @@ -0,0 +1,64 @@ +import { CE_DEFAULT_VALUE } from '#/configs/const-enum/CE_DEFAULT_VALUE'; +import type { ICommonOption } from '#/configs/interfaces/ICommonOption'; +import { getCwd } from '#/configs/modules/getCwd'; +import { container } from '#/modules/containers/container'; +import { SymbolLogger } from '#/modules/containers/keys/SymbolLogger'; +import { betterMkdir } from '#/modules/files/betterMkdir'; +import { getGlobFiles } from '#/modules/files/getGlobFiles'; +import { getOutputDirectory } from '#/modules/files/getOutputDirectory'; +import type { Logger } from '#/modules/loggers/Logger'; +import { createLogger } from '#/modules/loggers/createLogger'; +import { defaultExclude } from '#/modules/scopes/defaultExclude'; +import { getTemplatePath } from '#/templates/modules/getTemplatePath'; +import { Glob } from 'glob'; +import { isError } from 'my-easy-fp'; +import { getDirname, startSepRemove } from 'my-node-fp'; +import fs from 'node:fs'; +import pathe from 'pathe'; + +export async function ejecting(option: Pick) { + createLogger(); + const logger = container.resolve(SymbolLogger); + + try { + const outputDirPath = await getOutputDirectory(option, getCwd(process.env)); + const originTemplateDirPath = await getTemplatePath(CE_DEFAULT_VALUE.TEMPLATES_PATH); + const targetTemplateDirPath = + option.output == null ? pathe.join(outputDirPath, CE_DEFAULT_VALUE.TEMPLATES_PATH) : outputDirPath; + + logger.info('Output directory: ', targetTemplateDirPath); + + const originTemplateGlobPaths = new Glob(pathe.join(originTemplateDirPath, `**`, '*.eta'), { + absolute: true, + ignore: [...defaultExclude, 'config/**'], + cwd: originTemplateDirPath, + windowsPathsNoEscape: true, + }); + const originTemplateFilePaths = getGlobFiles(originTemplateGlobPaths); + + await Promise.all( + originTemplateFilePaths.map(async (originTemplateFilePath) => { + const subDirPath = await getDirname(originTemplateFilePath); + const subFilePath = startSepRemove(originTemplateFilePath.replace(subDirPath, '')); + const targetTemplateSubDirPath = pathe.join( + targetTemplateDirPath, + startSepRemove(subDirPath.replace(originTemplateDirPath, '')), + ); + + await betterMkdir(targetTemplateSubDirPath); + + const templateFileBuf = await fs.promises.readFile(originTemplateFilePath); + await fs.promises.writeFile(pathe.join(targetTemplateSubDirPath, subFilePath), templateFileBuf); + }), + ); + + logger.success('eject success: ', targetTemplateDirPath); + + return targetTemplateDirPath; + } catch (caught) { + const err = isError(caught, new Error('unknown error raised from createHtmlDocCommand')); + logger.error(err); + + return undefined; + } +} diff --git a/src/modules/commands/initializing.ts b/src/modules/commands/initializing.ts new file mode 100644 index 0000000..676855f --- /dev/null +++ b/src/modules/commands/initializing.ts @@ -0,0 +1,38 @@ +import { CE_DEFAULT_VALUE } from '#/configs/const-enum/CE_DEFAULT_VALUE'; +import { getConfigContent } from '#/configs/modules/getConfigContent'; +import { applyPrettier } from '#/creators/applyPretter'; +import { container } from '#/modules/containers/container'; +import { SymbolLogger } from '#/modules/containers/keys/SymbolLogger'; +import { SymbolTemplateRenderer } from '#/modules/containers/keys/SymbolTemplateRenderer'; +import type { Logger } from '#/modules/loggers/Logger'; +import { createLogger } from '#/modules/loggers/createLogger'; +import { TemplateRenderer } from '#/templates/TemplateRenderer'; +import { loadTemplates } from '#/templates/modules/loadTemplates'; +import { asValue } from 'awilix'; +import fs from 'fs'; +import { isError } from 'my-easy-fp'; + +export async function initializing() { + createLogger(); + const logger = container.resolve(SymbolLogger); + + try { + const templates = await loadTemplates(); + const renderer = new TemplateRenderer(templates.template, templates.default); + + container.register(SymbolTemplateRenderer, asValue(renderer)); + + const rawConfig = await getConfigContent(); + const prettiered = await applyPrettier(rawConfig, 'json'); + + await fs.promises.writeFile(CE_DEFAULT_VALUE.CONFIG_FILE_NAME, prettiered); + logger.info(`${CE_DEFAULT_VALUE.CONFIG_FILE_NAME} file created`); + + return rawConfig; + } catch (caught) { + const err = isError(caught, new Error('unknown error raised from createHtmlDocCommand')); + logger.error(err); + + return undefined; + } +} diff --git a/src/modules/containers/keys/SymbolLogger.ts b/src/modules/containers/keys/SymbolLogger.ts new file mode 100644 index 0000000..a444c12 --- /dev/null +++ b/src/modules/containers/keys/SymbolLogger.ts @@ -0,0 +1 @@ +export const SymbolLogger = Symbol('symbol-logger'); diff --git a/src/modules/loggers/Logger.ts b/src/modules/loggers/Logger.ts new file mode 100644 index 0000000..b7b3e3a --- /dev/null +++ b/src/modules/loggers/Logger.ts @@ -0,0 +1,57 @@ +import consola, { LogLevel, type InputLogObject, type LogType } from 'consola'; + +export class Logger { + #enable: boolean; + + get enable() { + return this.#enable; + } + + set enable(value) { + this.#enable = value; + } + + constructor() { + this.#enable = false; + } + + get level() { + return consola.level; + } + + set level(level: LogLevel) { + consola.level = level; + } + + logging(level: LogType, message: InputLogObject | any, ...args: any[]) { + if (this.#enable) { + consola[level](message, ...args); + } + } + + info = this.logging.bind(this, 'info'); + + warn = this.logging.bind(this, 'warn'); + + silent = this.logging.bind(this, 'silent'); + + error = this.logging.bind(this, 'error'); + + success = this.logging.bind(this, 'success'); + + fail = this.logging.bind(this, 'fail'); + + fatal = this.logging.bind(this, 'fatal'); + + debug = this.logging.bind(this, 'debug'); + + trace = this.logging.bind(this, 'trace'); + + verbose = this.logging.bind(this, 'verbose'); + + ready = this.logging.bind(this, 'ready'); + + box = this.logging.bind(this, 'box'); + + log = this.logging.bind(this, 'log'); +} diff --git a/src/modules/loggers/__tests__/logger.test.ts b/src/modules/loggers/__tests__/logger.test.ts new file mode 100644 index 0000000..d489467 --- /dev/null +++ b/src/modules/loggers/__tests__/logger.test.ts @@ -0,0 +1,34 @@ +import { Logger } from '#/modules/loggers/Logger'; +import { LogLevels } from 'consola'; +import { describe, expect, it } from 'vitest'; + +describe('Logger', () => { + it('logging message display', () => { + const logger = new Logger(); + logger.enable = true; + + logger.info('test 01'); + logger.warn('test 02'); + logger.silent('test 03'); + logger.success('test 04'); + logger.fail('test 05'); + logger.fatal('test 06'); + logger.debug('test 07'); + logger.trace('test 08'); + logger.verbose('test 09'); + logger.ready('test 10'); + logger.box('test 11'); + logger.log('test 12'); + logger.error('test 13'); + }); + + it('getter/setter', () => { + const logger = new Logger(); + + logger.enable = false; + logger.level = LogLevels.warn; + + expect(logger.enable).toBeFalsy(); + expect(logger.level).toEqual(LogLevels.warn); + }); +}); diff --git a/src/modules/loggers/createLogger.ts b/src/modules/loggers/createLogger.ts new file mode 100644 index 0000000..2f43e33 --- /dev/null +++ b/src/modules/loggers/createLogger.ts @@ -0,0 +1,11 @@ +import { container } from '#/modules/containers/container'; +import { SymbolLogger } from '#/modules/containers/keys/SymbolLogger'; +import { Logger } from '#/modules/loggers/Logger'; +import { asValue } from 'awilix'; + +export function createLogger() { + if (!container.hasRegistration(SymbolLogger)) { + const logger = new Logger(); + container.register(SymbolLogger, asValue(logger)); + } +}