Skip to content

Commit

Permalink
feat(module): add new functional interface
Browse files Browse the repository at this point in the history
- add new functional interface
  - building, cleaning, initing, ejecting
  • Loading branch information
imjuni committed Apr 8, 2024
1 parent f10ddbb commit bad88ee
Show file tree
Hide file tree
Showing 15 changed files with 507 additions and 293 deletions.
10 changes: 10 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
2 changes: 2 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ 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';
import yargs, { type CommandModule } from 'yargs';
import { hideBin } from 'yargs/helpers';

sourceMapSupport.install();
createLogger();

const buildCmdModule: CommandModule<IBuildCommandOption, IBuildCommandOption> = {
command: CE_COMMAND_LIST.BUILD,
Expand Down
186 changes: 19 additions & 167 deletions src/cli/commands/buildDocumentCommandHandler.ts
Original file line number Diff line number Diff line change
@@ -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<IReason> => isFail(relationRecord))
.map((relationRecord) => relationRecord.fail)
.flat();

failRelations.forEach((relation) => consola.warn(relation.message));

const passRelations = relationRecords
.filter((relation): relation is IPass<IRelationRecord[]> => 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<DataSource>(SymbolDataSource);
if (dataSource != null) {
await dataSource.destroy();
}
const logger = container.resolve<Logger>(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);
}
79 changes: 19 additions & 60 deletions src/cli/commands/cleanDocumentCommandHandler.ts
Original file line number Diff line number Diff line change
@@ -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<Logger>(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);
}
28 changes: 9 additions & 19 deletions src/cli/commands/initConfigCommandHandler.ts
Original file line number Diff line number Diff line change
@@ -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<Logger>(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;
}
Loading

0 comments on commit bad88ee

Please sign in to comment.