From 17df5575f9a2c87b4d840999165a8a307e137da7 Mon Sep 17 00:00:00 2001 From: ByungJoon Lee Date: Sun, 7 Apr 2024 23:39:49 +0900 Subject: [PATCH] feat(di): add class container for dependency injection - add class container for dependency injection - add new testcase - migrate template file: keep tempalte file format, not TypeScript source code --- .configs/jest.setup.cjs | 1 - .github/workflows/ci.yml | 27 ++-- .vscode/launch.json | 36 ----- jest.config.cjs | 14 -- package.json | 8 +- pnpm-lock.yaml | 22 ++- src/cli.ts | 1 + .../commands/buildDocumentCommandHandler.ts | 30 +++- .../commands/cleanDocumentCommandHandler.ts | 21 +-- .../commands/templateEjectCommandHandler.ts | 73 ++++----- src/common/__tests__/metadata.test.ts | 17 +- src/common/__tests__/version.test.ts | 4 +- src/common/getMetadata.ts | 5 +- src/common/getVersion.ts | 8 +- src/configs/const-enum/CE_DEFAULT_VALUE.ts | 2 + src/configs/modules/getConfigContent.ts | 44 +++--- src/creators/createHtml.ts | 30 ++-- src/creators/createImageHtml.ts | 22 ++- src/creators/createMarkdown.ts | 15 +- src/creators/createPdfHtml.ts | 22 ++- src/creators/getRenderData.ts | 2 +- src/creators/writeToImage.ts | 16 +- src/creators/writeToPdf.ts | 8 +- src/databases/__tests__/alasql.test.ts | 16 +- src/databases/flushDatabase.ts | 10 +- src/databases/openDatabase.ts | 8 +- src/modules/containers/container.ts | 3 + .../containers/keys/SymbolDataSource.ts | 1 + .../containers/keys/SymbolDefaultTemplate.ts | 1 + src/modules/containers/keys/SymbolTemplate.ts | 1 + .../containers/keys/SymbolTemplateRenderer.ts | 1 + .../files/__tests__/better.mkdir.test.ts | 34 ++++ .../files}/__tests__/file.test.ts | 16 +- src/modules/files/__tests__/glob.test.ts | 53 +++++++ src/modules/files/betterMkdir.ts | 20 +++ src/{tools => modules}/files/getFindFile.ts | 0 src/modules/files/getGlobFiles.ts | 12 ++ .../files/getOutputDirectory.ts | 12 +- src/{tools => modules}/getPuppeteerConfig.ts | 0 .../getSlashEndRoutePath.ts | 0 src/modules/scopes/defaultExclude.ts | 1 + src/template/cosnt-enum/CE_TEMPLATE_NAME.ts | 32 ---- src/template/defaultTemplates.ts | 55 ------- src/template/evaluateTemplate.ts | 20 --- src/template/html/mermaid-script.ts | 2 - src/template/html/style.ts | 149 ------------------ src/template/image/mermaid-script.ts | 2 - src/template/image/style.ts | 56 ------- src/template/loadTemplates.ts | 94 ----------- src/template/markdown/document.ts | 16 -- src/template/markdown/mermaid.ts | 7 - src/template/markdown/toc.ts | 12 -- src/template/mermaid/document.ts | 12 -- src/template/mermaid/entity.ts | 13 -- src/template/pdf/mermaid-script.ts | 2 - src/template/pdf/style.ts | 60 ------- src/templates/TemplateRenderer.ts | 35 ++++ src/templates/cosnt-enum/CE_TEMPLATE_NAME.ts | 28 ++++ src/templates/interfaces/ITemplate.ts | 4 + .../modules/__tests__/load.template.test.ts | 17 ++ .../modules/__tests__/template.path.test.ts | 58 +++++++ .../modules/__tests__/template.test.ts | 43 +++++ src/templates/modules/getTemplate.ts | 18 +++ src/templates/modules/getTemplatePath.ts | 33 ++++ src/templates/modules/getTemplates.ts | 28 ++++ src/templates/modules/loadTemplates.ts | 52 ++++++ src/tools/__tests__/template.test.ts | 111 ------------- src/tools/files/getTemplatePath.ts | 38 ----- src/typeorm/columns/__tests__/column.test.ts | 6 +- .../columns/__tests__/column.tools.test.ts | 18 ++- src/typeorm/columns/getComment.ts | 2 +- src/typeorm/entities/__tests__/entity.test.ts | 12 +- src/typeorm/getDataSource.ts | 4 +- .../__tests__/dedupe.relation.test.ts | 8 +- .../relations/__tests__/relation.test.ts | 32 ++-- .../__tests__/relation.tools.test.ts | 10 +- .../config/init-command.eta | 4 +- .../html/document-toc.eta | 4 +- .../html/document.eta | 19 +-- .../html/mermaid-diagram.eta | 26 ++- .../html/mermaid-toc.eta | 4 +- .../mermaid.ts => templates/html/mermaid.eta | 24 ++- templates/html/style.eta | 149 ++++++++++++++++++ .../html/table.ts => templates/html/table.eta | 4 +- .../image/document.eta | 17 +- templates/image/mermaid-diagram.eta | 49 ++++++ templates/image/style.eta | 56 +++++++ templates/markdown/document.eta | 16 ++ templates/markdown/mermaid-diagram.eta | 49 ++++++ .../table.ts => templates/markdown/table.eta | 3 +- templates/markdown/toc.eta | 9 ++ .../pdf/document-toc.eta | 10 +- .../document.ts => templates/pdf/document.eta | 23 +-- templates/pdf/mermaid-diagram.eta | 49 ++++++ templates/pdf/style.eta | 60 +++++++ .../pdf/table.ts => templates/pdf/table.eta | 4 +- vitest.config.ts | 2 +- 97 files changed, 1274 insertions(+), 1013 deletions(-) delete mode 100644 .configs/jest.setup.cjs delete mode 100644 jest.config.cjs create mode 100644 src/modules/containers/container.ts create mode 100644 src/modules/containers/keys/SymbolDataSource.ts create mode 100644 src/modules/containers/keys/SymbolDefaultTemplate.ts create mode 100644 src/modules/containers/keys/SymbolTemplate.ts create mode 100644 src/modules/containers/keys/SymbolTemplateRenderer.ts create mode 100644 src/modules/files/__tests__/better.mkdir.test.ts rename src/{tools => modules/files}/__tests__/file.test.ts (89%) create mode 100644 src/modules/files/__tests__/glob.test.ts create mode 100644 src/modules/files/betterMkdir.ts rename src/{tools => modules}/files/getFindFile.ts (100%) create mode 100644 src/modules/files/getGlobFiles.ts rename src/{tools => modules}/files/getOutputDirectory.ts (64%) rename src/{tools => modules}/getPuppeteerConfig.ts (100%) rename src/{tools => modules}/getSlashEndRoutePath.ts (100%) create mode 100644 src/modules/scopes/defaultExclude.ts delete mode 100644 src/template/cosnt-enum/CE_TEMPLATE_NAME.ts delete mode 100644 src/template/defaultTemplates.ts delete mode 100644 src/template/evaluateTemplate.ts delete mode 100644 src/template/html/mermaid-script.ts delete mode 100644 src/template/html/style.ts delete mode 100644 src/template/image/mermaid-script.ts delete mode 100644 src/template/image/style.ts delete mode 100644 src/template/loadTemplates.ts delete mode 100644 src/template/markdown/document.ts delete mode 100644 src/template/markdown/mermaid.ts delete mode 100644 src/template/markdown/toc.ts delete mode 100644 src/template/mermaid/document.ts delete mode 100644 src/template/mermaid/entity.ts delete mode 100644 src/template/pdf/mermaid-script.ts delete mode 100644 src/template/pdf/style.ts create mode 100644 src/templates/TemplateRenderer.ts create mode 100644 src/templates/cosnt-enum/CE_TEMPLATE_NAME.ts create mode 100644 src/templates/interfaces/ITemplate.ts create mode 100644 src/templates/modules/__tests__/load.template.test.ts create mode 100644 src/templates/modules/__tests__/template.path.test.ts create mode 100644 src/templates/modules/__tests__/template.test.ts create mode 100644 src/templates/modules/getTemplate.ts create mode 100644 src/templates/modules/getTemplatePath.ts create mode 100644 src/templates/modules/getTemplates.ts create mode 100644 src/templates/modules/loadTemplates.ts delete mode 100644 src/tools/__tests__/template.test.ts delete mode 100644 src/tools/files/getTemplatePath.ts rename src/template/config/init-command.ts => templates/config/init-command.eta (99%) rename src/template/html/document-toc.ts => templates/html/document-toc.eta (85%) rename src/template/html/document.ts => templates/html/document.eta (85%) rename src/template/mermaid/relation.ts => templates/html/mermaid-diagram.eta (62%) rename src/template/html/mermaid-toc.ts => templates/html/mermaid-toc.eta (80%) rename src/template/html/mermaid.ts => templates/html/mermaid.eta (80%) create mode 100644 templates/html/style.eta rename src/template/html/table.ts => templates/html/table.eta (99%) rename src/template/image/document.ts => templates/image/document.eta (71%) create mode 100644 templates/image/mermaid-diagram.eta create mode 100644 templates/image/style.eta create mode 100644 templates/markdown/document.eta create mode 100644 templates/markdown/mermaid-diagram.eta rename src/template/markdown/table.ts => templates/markdown/table.eta (95%) create mode 100644 templates/markdown/toc.eta rename src/template/pdf/document-toc.ts => templates/pdf/document-toc.eta (71%) rename src/template/pdf/document.ts => templates/pdf/document.eta (71%) create mode 100644 templates/pdf/mermaid-diagram.eta create mode 100644 templates/pdf/style.eta rename src/template/pdf/table.ts => templates/pdf/table.eta (98%) diff --git a/.configs/jest.setup.cjs b/.configs/jest.setup.cjs deleted file mode 100644 index 09ffd35..0000000 --- a/.configs/jest.setup.cjs +++ /dev/null @@ -1 +0,0 @@ -jest.setTimeout(60000); diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e89cc03..6e30d58 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,14 +20,19 @@ jobs: # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - - uses: actions/checkout@v3 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} - cache: 'npm' - - run: npm ci - - run: npm run build - - run: npm run test:silent - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v3 + with: + version: 8 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'pnpm' + - run: pnpm i + - run: pnpm run build + - run: pnpm run test + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.vscode/launch.json b/.vscode/launch.json index 2d12fe3..f988a06 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,47 +4,11 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ - { - "name": "Jest", - "request": "launch", - "runtimeArgs": ["run-script", "test:debug", "${input:testPath}"], - "runtimeExecutable": "npm", - "skipFiles": ["/**"], - "stopOnEntry": true, - "type": "node" - }, - { - "type": "node", - "request": "launch", - "name": "Launch Test", - "args": ["--runInBand", "--no-cache", "${input:testPath}"], - "runtimeArgs": ["-r", "ts-node/register", "--inspect"], - "stopOnEntry": false, - "sourceMaps": true, - "program": "${workspaceFolder}/node_modules/jest/bin/jest", - "env": { - "TS_NODE_PROJECT": "tsconfig.json" - } - }, { "type": "node", "request": "attach", "name": "Attach Process", "port": 9229 } - ], - "inputs": [ - { - "type": "promptString", - "id": "testPath", - "description": "Path Name your testcase", - "default": "" - }, - { - "type": "promptString", - "id": "testName", - "description": "Name your testcase", - "default": "" - } ] } diff --git a/jest.config.cjs b/jest.config.cjs deleted file mode 100644 index 440d915..0000000 --- a/jest.config.cjs +++ /dev/null @@ -1,14 +0,0 @@ -const { pathsToModuleNameMapper } = require('ts-jest'); - -module.exports = { - testEnvironment: 'node', - moduleFileExtensions: ['ts', 'tsx', 'js'], - transform: { - '^.+\\.(ts|tsx)$': ['ts-jest'], - }, - testMatch: ['**/__tests__/*.(ts|tsx)', '**/__test__/*.(ts|tsx)'], - testPathIgnorePatterns: ['/node_modules/', 'examples/', 'dist', 'test-config.ts'], - setupFilesAfterEnv: ['/.configs/jest.setup.cjs'], - moduleDirectories: ['node_modules', 'src', __dirname], - moduleNameMapper: pathsToModuleNameMapper({ '#/*': ['src/*'] }, { prefix: '/' }), -}; diff --git a/package.json b/package.json index b1a7a36..7a9d732 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "bundle-cli": "FORMAT=cjs node .configs/esbuild.cli.mjs", "bundle": "run-s clean bundle-cli bundle-lib", "build": "pnpm run clean && tsc --incremental --project tsconfig.prod.json", - "test": "vitest --run", + "test": "vitest --run --coverage", "lint": "eslint --cache .", "lint-staged": "lint-staged", "prettier": "prettier --write src/**/*.ts", @@ -70,7 +70,8 @@ } }, "files": [ - "dist" + "dist", + "templates" ], "bin": { "erdia": "dist/cjs/cli.cjs" @@ -129,6 +130,7 @@ "dependencies": { "@maeum/cli-logo": "^1.0.0", "alasql": "^4.1.3", + "awilix": "^10.0.2", "chalk": "^4.1.2", "compare-versions": "^6.1.0", "consola": "^3.2.3", @@ -140,6 +142,7 @@ "filenamify": "^4.3.0", "find-up": "^5.0.0", "fuse.js": "^6.6.2", + "glob": "^10.3.12", "globby": "^11.1.0", "inquirer": "^8.2.4", "inquirer-autocomplete-prompt": "^2.0.0", @@ -150,6 +153,7 @@ "my-easy-fp": "^0.22.0", "my-node-fp": "^0.10.3", "my-only-either": "^1.1.2", + "pathe": "^1.1.2", "prettier": "^3.0.3", "puppeteer": "^22.6.1", "read-pkg": "^5.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 298a1e4..2bb18da 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ dependencies: alasql: specifier: ^4.1.3 version: 4.3.2 + awilix: + specifier: ^10.0.2 + version: 10.0.2 chalk: specifier: ^4.1.2 version: 4.1.2 @@ -44,6 +47,9 @@ dependencies: fuse.js: specifier: ^6.6.2 version: 6.6.2 + glob: + specifier: ^10.3.12 + version: 10.3.12 globby: specifier: ^11.1.0 version: 11.1.0 @@ -74,6 +80,9 @@ dependencies: my-only-either: specifier: ^1.1.2 version: 1.3.0 + pathe: + specifier: ^1.1.2 + version: 1.1.2 prettier: specifier: ^3.0.3 version: 3.2.5 @@ -1709,6 +1718,14 @@ packages: possible-typed-array-names: 1.0.0 dev: true + /awilix@10.0.2: + resolution: {integrity: sha512-hFatb7eZFdtiWjjmGRSm/K/uxZpmcBlM+YoeMB3VpOPXk3xa6+7zctg3LRbUzoimom5bwGrePF0jXReO6b4zNQ==} + engines: {node: '>=14.0.0'} + dependencies: + camel-case: 4.1.2 + fast-glob: 3.3.2 + dev: false + /b4a@1.6.6: resolution: {integrity: sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==} dev: false @@ -1872,7 +1889,6 @@ packages: dependencies: pascal-case: 3.1.2 tslib: 2.6.2 - dev: true /capital-case@1.0.4: resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==} @@ -4475,7 +4491,6 @@ packages: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: tslib: 2.6.2 - dev: true /lru-cache@10.2.0: resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} @@ -4961,7 +4976,6 @@ packages: dependencies: lower-case: 2.0.2 tslib: 2.6.2 - dev: true /node-abi@3.56.0: resolution: {integrity: sha512-fZjdhDOeRcaS+rcpve7XuwHBmktS1nS1gzgghwKUQQ8nTy2FdSDr6ZT8k6YhvlJeHmmQMYiT/IH9hfco5zeW2Q==} @@ -5252,7 +5266,6 @@ packages: dependencies: no-case: 3.0.4 tslib: 2.6.2 - dev: true /path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} @@ -5310,7 +5323,6 @@ packages: /pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - dev: true /pathval@1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} diff --git a/src/cli.ts b/src/cli.ts index a79788d..fc19fdd 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -10,6 +10,7 @@ import { CE_COMMAND_LIST } from '#/configs/const-enum/CE_COMMAND_LIST'; import type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption'; import type { ICommonOption } from '#/configs/interfaces/ICommonOption'; import { preLoadConfig } from '#/configs/modules/preLoadConfig'; +import '#/modules/containers/container'; import consola from 'consola'; import { isError } from 'my-easy-fp'; import sourceMapSupport from 'source-map-support'; diff --git a/src/cli/commands/buildDocumentCommandHandler.ts b/src/cli/commands/buildDocumentCommandHandler.ts index 7d2b9da..75a6e23 100644 --- a/src/cli/commands/buildDocumentCommandHandler.ts +++ b/src/cli/commands/buildDocumentCommandHandler.ts @@ -16,7 +16,14 @@ import { flushDatabase } from '#/databases/flushDatabase'; import type { IRelationRecord } from '#/databases/interfaces/IRelationRecord'; import { openDatabase } from '#/databases/openDatabase'; import { processDatabase } from '#/databases/processDatabase'; -import { loadTemplates } from '#/template/loadTemplates'; +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'; @@ -24,6 +31,7 @@ import { getIndexRecords } from '#/typeorm/indices/getIndexRecords'; import { dedupeManaToManyRelationRecord } from '#/typeorm/relations/dedupeManaToManyRelationRecord'; import { getRelationRecords } from '#/typeorm/relations/getRelationRecords'; import { showLogo } from '@maeum/cli-logo'; +import { asValue } from 'awilix'; import chalk from 'chalk'; import consola from 'consola'; import fastSafeStringify from 'fast-safe-stringify'; @@ -49,16 +57,19 @@ export async function buildDocumentCommandHandler(option: IBuildCommandOption) { consola.info(`connection initialize: "${chalk.yellowBright(`${option.dataSourcePath}`)}"`); const dataSource = await getDataSource(option); - await dataSource.initialize(); + const [templates] = await Promise.all([await loadTemplates(option), await dataSource.initialize()]); + const renderer = new TemplateRenderer(templates.default, templates.template); if (isFalse(dataSource.isInitialized)) { throw new Error(`Cannot initialize in ${fastSafeStringify(dataSource.options, undefined, 2)}`); } - localDataSource = dataSource; - await loadTemplates(option); + 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(dataSource, option); + const metadata = await getMetadata(option); consola.success('connection initialized'); consola.info(`version: ${metadata.version}`); @@ -113,7 +124,12 @@ export async function buildDocumentCommandHandler(option: IBuildCommandOption) { }; const documents = await createHtml(option, renderData); - await Promise.all(documents.map((document) => fs.promises.writeFile(document.filename, document.content))); + 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); @@ -125,6 +141,7 @@ export async function buildDocumentCommandHandler(option: IBuildCommandOption) { 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]; } @@ -137,6 +154,7 @@ export async function buildDocumentCommandHandler(option: IBuildCommandOption) { 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; } diff --git a/src/cli/commands/cleanDocumentCommandHandler.ts b/src/cli/commands/cleanDocumentCommandHandler.ts index 1d560d1..23de2e8 100644 --- a/src/cli/commands/cleanDocumentCommandHandler.ts +++ b/src/cli/commands/cleanDocumentCommandHandler.ts @@ -2,14 +2,17 @@ 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 { getOutputDirectory } from '#/tools/files/getOutputDirectory'; +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 path from 'path'; +import pathe from 'pathe'; import type { DataSource } from 'typeorm'; export async function cleanDocumentCommandHandler(option: ICommonOption) { @@ -33,17 +36,17 @@ export async function cleanDocumentCommandHandler(option: ICommonOption) { throw new Error(`Cannot initialize in ${fastSafeStringify(dataSource.options, undefined, 2)}`); } - localDataSource = dataSource; + container.register(SymbolDataSource, asValue(dataSource)); - const metadata = await getMetadata(dataSource, { ...option, versionFrom: 'package.json', projectName: 'app' }); + const metadata = await getMetadata({ ...option, versionFrom: 'package.json', projectName: 'app' }); const outputDir = await getOutputDirectory(option, getCwd(process.env)); const filenames = [ - path.join(outputDir, CE_DEFAULT_VALUE.HTML_MERMAID_FILENAME), - path.join(outputDir, CE_DEFAULT_VALUE.HTML_INDEX_FILENAME), - path.join(outputDir, `${metadata.name}.md`), - path.join(outputDir, `${metadata.name}.png`), - path.join(outputDir, `${metadata.name}.svg`), + pathe.join(outputDir, CE_DEFAULT_VALUE.HTML_MERMAID_FILENAME), + pathe.join(outputDir, CE_DEFAULT_VALUE.HTML_INDEX_FILENAME), + pathe.join(outputDir, `${metadata.name}.md`), + pathe.join(outputDir, `${metadata.name}.png`), + pathe.join(outputDir, `${metadata.name}.svg`), ]; await del(filenames); diff --git a/src/cli/commands/templateEjectCommandHandler.ts b/src/cli/commands/templateEjectCommandHandler.ts index 0be772b..d32493e 100644 --- a/src/cli/commands/templateEjectCommandHandler.ts +++ b/src/cli/commands/templateEjectCommandHandler.ts @@ -1,14 +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 { CE_TEMPLATE_NAME } from '#/template/cosnt-enum/CE_TEMPLATE_NAME'; -import { defaultTemplates } from '#/template/defaultTemplates'; -import { getOutputDirectory } from '#/tools/files/getOutputDirectory'; +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 { showLogo } from '@maeum/cli-logo'; import consola from 'consola'; import fs from 'fs'; -import { isFalse } from 'my-easy-fp'; -import { exists, getDirname } from 'my-node-fp'; -import path from 'path'; +import { Glob } from 'glob'; +import { getDirname } from 'my-node-fp'; +import pathe from 'pathe'; export async function templateEjectCommandHandler(option: Pick) { if (option.showLogo != null) { @@ -22,49 +25,33 @@ export async function templateEjectCommandHandler(option: Pick { - const filename = path.resolve(path.join(templateDir, `${template}.eta`)); - const dirname = await getDirname(filename); + const originTemplateGlobPaths = new Glob(pathe.join(originTemplateDirPath, `**`, '*.eta'), { + absolute: true, + ignore: defaultExclude, + cwd: originTemplateDirPath, + windowsPathsNoEscape: true, + }); + const originTemplateFilePaths = getGlobFiles(originTemplateGlobPaths); - if (isFalse(await exists(dirname))) { - await fs.promises.mkdir(dirname, { recursive: true }); - } + await Promise.all( + originTemplateFilePaths.map(async (originTemplateFilePath) => { + const subDirPath = await getDirname(originTemplateFilePath); + const subFilePath = originTemplateFilePath.replace(subDirPath, ''); + const targetTemplateSubDirPath = pathe.join(targetTemplateDirPath, subDirPath); - await fs.promises.writeFile(filename, defaultTemplates[template]); - }; + await betterMkdir(targetTemplateSubDirPath); - await Promise.all( - [ - CE_TEMPLATE_NAME.HTML_DOCUMENT_TOC, - CE_TEMPLATE_NAME.HTML_DOCUMENT, - CE_TEMPLATE_NAME.HTML_MERMAID_SCRIPT, - CE_TEMPLATE_NAME.HTML_MERMAID_TOC, - CE_TEMPLATE_NAME.HTML_MERMAID, - CE_TEMPLATE_NAME.HTML_STYLE, - CE_TEMPLATE_NAME.HTML_TABLE, - CE_TEMPLATE_NAME.IMAGE_DOCUMENT, - CE_TEMPLATE_NAME.IMAGE_MERMAID_SCRIPT, - CE_TEMPLATE_NAME.IMAGE_STYLE, - CE_TEMPLATE_NAME.MARKDOWN_DOCUMENT, - CE_TEMPLATE_NAME.MARKDOWN_MERMAID, - CE_TEMPLATE_NAME.MARKDOWN_TABLE, - CE_TEMPLATE_NAME.MARKDOWN_TOC, - CE_TEMPLATE_NAME.MERMAID_DOCUMENT, - CE_TEMPLATE_NAME.MERMAID_ENTITY, - CE_TEMPLATE_NAME.MERMAID_RELATION, - CE_TEMPLATE_NAME.PDF_DOCUMENT_TOC, - CE_TEMPLATE_NAME.PDF_DOCUMENT, - CE_TEMPLATE_NAME.PDF_MERMAID_SCRIPT, - CE_TEMPLATE_NAME.PDF_STYLE, - CE_TEMPLATE_NAME.PDF_TABLE, - ].map((template) => writeFile(template)), + const templateFileBuf = await fs.promises.readFile(originTemplateFilePath); + await fs.promises.writeFile(pathe.join(subFilePath, subFilePath), templateFileBuf); + }), ); - consola.success('eject success: ', templateDir); + consola.success('eject success: ', targetTemplateDirPath); - return templateDir; + return targetTemplateDirPath; } diff --git a/src/common/__tests__/metadata.test.ts b/src/common/__tests__/metadata.test.ts index 688cb43..c9b4f3d 100644 --- a/src/common/__tests__/metadata.test.ts +++ b/src/common/__tests__/metadata.test.ts @@ -3,7 +3,10 @@ import { getDatabaseName } from '#/common/getDatabaseName'; import { getMetadata } from '#/common/getMetadata'; import { getPackageName } from '#/common/getPackageName'; import { getProjectName } from '#/common/getProjectName'; -import { getFindFile } from '#/tools/files/getFindFile'; +import { container } from '#/modules/containers/container'; +import { SymbolDataSource } from '#/modules/containers/keys/SymbolDataSource'; +import { getFindFile } from '#/modules/files/getFindFile'; +import { asValue } from 'awilix'; import dayjs from 'dayjs'; import * as findUp from 'find-up'; import { isError } from 'my-easy-fp'; @@ -47,10 +50,8 @@ describe('getMetadata', () => { .spyOn(readPkg, 'default') .mockImplementationOnce(() => Promise.resolve({ name: 'erdia', version: '1.1.1' })); - const metadata = await getMetadata( - { options: { database: 'i-am-database' } }, - { ...env.buildOption, versionFrom: 'timestamp' }, - ); + container.register(SymbolDataSource, asValue({ options: { database: 'i-am-database' } })); + const metadata = await getMetadata({ ...env.buildOption, versionFrom: 'timestamp' }); tspSpyOn01.mockRestore(); tspSpyOn02.mockRestore(); @@ -77,10 +78,8 @@ describe('getMetadata', () => { .spyOn(readPkg, 'default') .mockImplementationOnce(() => Promise.resolve({ name: '@maeum_pet-store', version: '1.1.1' })); - const metadata = await getMetadata( - { options: { database: 'i-am-database' } }, - { ...env.buildOption, versionFrom: 'timestamp' }, - ); + container.register(SymbolDataSource, asValue({ options: { database: 'i-am-database' } })); + const metadata = await getMetadata({ ...env.buildOption, versionFrom: 'timestamp' }); tspSpyOn01.mockRestore(); tspSpyOn02.mockRestore(); diff --git a/src/common/__tests__/version.test.ts b/src/common/__tests__/version.test.ts index 9b92845..d061dc7 100644 --- a/src/common/__tests__/version.test.ts +++ b/src/common/__tests__/version.test.ts @@ -1,7 +1,7 @@ import { getFileVersion } from '#/common/getFileVersion'; import { getVersion } from '#/common/getVersion'; -import * as getFindFile from '#/tools/files/getFindFile'; -import * as getOutputDirectory from '#/tools/files/getOutputDirectory'; +import * as getFindFile from '#/modules/files/getFindFile'; +import * as getOutputDirectory from '#/modules/files/getOutputDirectory'; import dayjs from 'dayjs'; import fs from 'fs'; import { describe, expect, test, vitest } from 'vitest'; diff --git a/src/common/getMetadata.ts b/src/common/getMetadata.ts index f9c5f6c..e3ced96 100644 --- a/src/common/getMetadata.ts +++ b/src/common/getMetadata.ts @@ -2,14 +2,17 @@ import { getProjectName } from '#/common/getProjectName'; import { getVersion } from '#/common/getVersion'; import type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption'; import type { IRecordMetadata } from '#/databases/interfaces/IRecordMetadata'; +import { container } from '#/modules/containers/container'; +import { SymbolDataSource } from '#/modules/containers/keys/SymbolDataSource'; import dayjs from 'dayjs'; import filenamify from 'filenamify'; import readPkg from 'read-pkg'; +import type { DataSource } from 'typeorm'; export async function getMetadata( - dataSource: { options: { database?: string | Uint8Array | undefined } }, option: Pick, ): Promise { + const dataSource = container.resolve(SymbolDataSource); const json = await readPkg({ normalize: false }); const rawName = await getProjectName(dataSource, json, option); const name = filenamify(rawName, { replacement: '_' }); diff --git a/src/common/getVersion.ts b/src/common/getVersion.ts index bca902b..d223357 100644 --- a/src/common/getVersion.ts +++ b/src/common/getVersion.ts @@ -2,11 +2,11 @@ import { getFileVersion } from '#/common/getFileVersion'; import { CE_DEFAULT_VALUE } from '#/configs/const-enum/CE_DEFAULT_VALUE'; import type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption'; import { getCwd } from '#/configs/modules/getCwd'; -import { getFindFile } from '#/tools/files/getFindFile'; -import { getOutputDirectory } from '#/tools/files/getOutputDirectory'; +import { getFindFile } from '#/modules/files/getFindFile'; +import { getOutputDirectory } from '#/modules/files/getOutputDirectory'; import dayjs from 'dayjs'; import fs from 'fs'; -import path from 'path'; +import pathe from 'pathe'; async function getVersionFilename( option: Pick, @@ -14,7 +14,7 @@ async function getVersionFilename( ) { if (option.versionPath != null) { const filename = await getFindFile( - path.join(await getOutputDirectory({ output: option.versionPath }, getCwd(process.env)), versionFilename), + pathe.join(await getOutputDirectory({ output: option.versionPath }, getCwd(process.env)), versionFilename), { cwd: getCwd(process.env) }, ); diff --git a/src/configs/const-enum/CE_DEFAULT_VALUE.ts b/src/configs/const-enum/CE_DEFAULT_VALUE.ts index 22d687e..9a48e91 100644 --- a/src/configs/const-enum/CE_DEFAULT_VALUE.ts +++ b/src/configs/const-enum/CE_DEFAULT_VALUE.ts @@ -10,6 +10,8 @@ export const CE_DEFAULT_VALUE = { DATABASE_FILENAME: 'erdiadb.json', VERSION_FILENAME: '.erdiaverrc', + TEMPLATES_PATH: 'templates', + DATA_SOURCE_FILE_FUZZY_SCORE_LIMIT: 50, OUTPUT_DIRECTORY_FUZZY_SCORE_LIMIT: 50, } as const; diff --git a/src/configs/modules/getConfigContent.ts b/src/configs/modules/getConfigContent.ts index 643473f..dde40c4 100644 --- a/src/configs/modules/getConfigContent.ts +++ b/src/configs/modules/getConfigContent.ts @@ -8,13 +8,17 @@ 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 { CE_TEMPLATE_NAME } from '#/template/cosnt-enum/CE_TEMPLATE_NAME'; -import { evaluateTemplate } from '#/template/evaluateTemplate'; +import { container } from '#/modules/containers/container'; +import { SymbolTemplateRenderer } from '#/modules/containers/keys/SymbolTemplateRenderer'; +import { getGlobFiles } from '#/modules/files/getGlobFiles'; +import { defaultExclude } from '#/modules/scopes/defaultExclude'; +import type { TemplateRenderer } from '#/templates/TemplateRenderer'; +import { CE_TEMPLATE_NAME } from '#/templates/cosnt-enum/CE_TEMPLATE_NAME'; import Fuse from 'fuse.js'; -import globby from 'globby'; +import { Glob } from 'glob'; import inquirer from 'inquirer'; import inquirerPrompt from 'inquirer-autocomplete-prompt'; -import path from 'node:path'; +import pathe from 'pathe'; export async function getConfigContent() { /** @@ -31,29 +35,31 @@ export async function getConfigContent() { * - png */ - const sourceFiles = await globby(['**/*.js', '**/*.cjs', '**/*.mjs', '**/*.ts', '**/*.cts', '**/*.mts'], { + const sourceGlobFiles = new Glob(['**/*.js', '**/*.cjs', '**/*.mjs', '**/*.ts', '**/*.cts', '**/*.mts'], { + absolute: true, + ignore: defaultExclude, cwd: process.cwd(), onlyFiles: true, - gitignore: true, - ignore: ['node_modules'], - dot: true, }); + const sourceFiles = getGlobFiles(sourceGlobFiles); - const everyFiles = await globby(['**/*'], { + const everyGlobFiles = new Glob(['**/*'], { + absolute: true, + ignore: defaultExclude, cwd: process.cwd(), - onlyFiles: true, - gitignore: true, - ignore: ['node_modules'], dot: true, + onlyFiles: true, }); + const everyFiles = getGlobFiles(everyGlobFiles); - const directories = await globby(['**'], { + const directoryGlobDirPaths = new Glob(['**/*'], { + absolute: true, + ignore: defaultExclude, cwd: process.cwd(), - onlyDirectories: true, - gitignore: true, - ignore: ['node_modules'], dot: true, + onlyDirectories: true, }); + const directories = getGlobFiles(directoryGlobDirPaths); const sourceFilesFuse = new Fuse(sourceFiles, { includeScore: true }); const everyFilesFuse = new Fuse(everyFiles, { includeScore: true }); @@ -210,11 +216,11 @@ export async function getConfigContent() { const templateDir = await (answer.isEjectTemplate ? templateEjectCommandHandler({ output: getCwd(process.env), showLogo: false }) : Promise.resolve(undefined)); - - const file = await evaluateTemplate(CE_TEMPLATE_NAME.CONFIG_JSON, { + const renderer = container.resolve(SymbolTemplateRenderer); + const file = await renderer.evaluate(CE_TEMPLATE_NAME.CONFIG_JSON, { config: { ...answer, - templatePath: templateDir != null ? path.relative(getCwd(process.env), templateDir) : templateDir, + templatePath: templateDir != null ? pathe.relative(getCwd(process.env), templateDir) : templateDir, versionFrom: answer.versionFrom != null ? answer.versionFrom : CE_ENTITY_VERSION_FROM.TIMESTAMP, config: CE_DEFAULT_VALUE.CONFIG_FILE_NAME, }, diff --git a/src/creators/createHtml.ts b/src/creators/createHtml.ts index e3847e4..a753614 100644 --- a/src/creators/createHtml.ts +++ b/src/creators/createHtml.ts @@ -5,11 +5,13 @@ import { getCwd } from '#/configs/modules/getCwd'; import { applyPrettier } from '#/creators/applyPretter'; import type { getRenderData } from '#/creators/getRenderData'; import type { IErdiaDocument } from '#/creators/interfaces/IErdiaDocument'; -import { CE_TEMPLATE_NAME } from '#/template/cosnt-enum/CE_TEMPLATE_NAME'; -import { evaluateTemplate } from '#/template/evaluateTemplate'; -import { getOutputDirectory } from '#/tools/files/getOutputDirectory'; +import { container } from '#/modules/containers/container'; +import { SymbolTemplateRenderer } from '#/modules/containers/keys/SymbolTemplateRenderer'; +import { getOutputDirectory } from '#/modules/files/getOutputDirectory'; +import type { TemplateRenderer } from '#/templates/TemplateRenderer'; +import { CE_TEMPLATE_NAME } from '#/templates/cosnt-enum/CE_TEMPLATE_NAME'; import consola from 'consola'; -import path from 'path'; +import pathe from 'pathe'; import type { AsyncReturnType } from 'type-fest'; async function getTables( @@ -21,13 +23,14 @@ async function getTables( return []; } - const rawTables = await evaluateTemplate(CE_TEMPLATE_NAME.HTML_DOCUMENT, renderData); + const renderer = container.resolve(SymbolTemplateRenderer); + const rawTables = await renderer.evaluate(CE_TEMPLATE_NAME.HTML_DOCUMENT, renderData); const prettiedTables = await applyPrettier(rawTables, 'html', option.prettierConfig); - const tablesFileName = path.join(outputDir, CE_DEFAULT_VALUE.HTML_INDEX_FILENAME); + const tablesFileName = pathe.join(outputDir, CE_DEFAULT_VALUE.HTML_INDEX_FILENAME); return [ { - dirname: path.resolve(outputDir), - filename: path.resolve(tablesFileName), + dirname: pathe.resolve(outputDir), + filename: pathe.resolve(tablesFileName), content: prettiedTables, }, ]; @@ -42,16 +45,17 @@ async function getDiagram( return []; } - const rawDiagram = await evaluateTemplate(CE_TEMPLATE_NAME.HTML_MERMAID, renderData); + const renderer = container.resolve(SymbolTemplateRenderer); + const rawDiagram = await renderer.evaluate(CE_TEMPLATE_NAME.HTML_MERMAID, renderData); const prettiedDiagram = await applyPrettier(rawDiagram, 'html', option.prettierConfig); const diagramFileName = option.components.includes(CE_OUTPUT_COMPONENT.TABLE) - ? path.join(outputDir, CE_DEFAULT_VALUE.HTML_MERMAID_FILENAME) - : path.join(outputDir, CE_DEFAULT_VALUE.HTML_INDEX_FILENAME); + ? pathe.join(outputDir, CE_DEFAULT_VALUE.HTML_MERMAID_FILENAME) + : pathe.join(outputDir, CE_DEFAULT_VALUE.HTML_INDEX_FILENAME); return [ { - dirname: path.resolve(outputDir), - filename: path.resolve(diagramFileName), + dirname: pathe.resolve(outputDir), + filename: pathe.resolve(diagramFileName), content: prettiedDiagram, }, ]; diff --git a/src/creators/createImageHtml.ts b/src/creators/createImageHtml.ts index 4195a8a..21b2545 100644 --- a/src/creators/createImageHtml.ts +++ b/src/creators/createImageHtml.ts @@ -2,28 +2,34 @@ import type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOpti import { applyPrettier } from '#/creators/applyPretter'; import type { getRenderData } from '#/creators/getRenderData'; import type { IErdiaDocument } from '#/creators/interfaces/IErdiaDocument'; -import { CE_TEMPLATE_NAME } from '#/template/cosnt-enum/CE_TEMPLATE_NAME'; -import { evaluateTemplate } from '#/template/evaluateTemplate'; +import { container } from '#/modules/containers/container'; +import { SymbolTemplateRenderer } from '#/modules/containers/keys/SymbolTemplateRenderer'; +import { betterMkdir } from '#/modules/files/betterMkdir'; +import type { TemplateRenderer } from '#/templates/TemplateRenderer'; +import { CE_TEMPLATE_NAME } from '#/templates/cosnt-enum/CE_TEMPLATE_NAME'; import { getDirname } from 'my-node-fp'; import { randomUUID } from 'node:crypto'; -import path from 'path'; +import pathe from 'pathe'; import type { AsyncReturnType } from 'type-fest'; export async function createImageHtml( option: IBuildCommandOption, renderData: AsyncReturnType, ): Promise { - const rawHtml = await evaluateTemplate(CE_TEMPLATE_NAME.IMAGE_DOCUMENT, { + const renderer = container.resolve(SymbolTemplateRenderer); + const rawHtml = await renderer.evaluate(CE_TEMPLATE_NAME.IMAGE_DOCUMENT, { ...renderData, option: { ...renderData.option, width: '200vw' }, }); + const prettiedHtml = await applyPrettier(rawHtml, 'html', option.prettierConfig); - const outputDir = await getDirname(option.output ?? process.cwd()); - const tempFileName = path.join(outputDir, `${randomUUID()}.html`); + const outputDirPath = option.output != null ? pathe.resolve(option.output) : process.cwd(); + await betterMkdir(outputDirPath); + const tempFileName = pathe.join(outputDirPath, `${randomUUID()}.html`); return { - dirname: path.resolve(outputDir), + dirname: await getDirname(outputDirPath), content: prettiedHtml, - filename: path.resolve(tempFileName), + filename: pathe.resolve(tempFileName), } satisfies IErdiaDocument; } diff --git a/src/creators/createMarkdown.ts b/src/creators/createMarkdown.ts index d8a2084..9f6d8b9 100644 --- a/src/creators/createMarkdown.ts +++ b/src/creators/createMarkdown.ts @@ -2,24 +2,27 @@ import type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOpti import { applyPrettier } from '#/creators/applyPretter'; import type { getRenderData } from '#/creators/getRenderData'; import type { IErdiaDocument } from '#/creators/interfaces/IErdiaDocument'; -import { CE_TEMPLATE_NAME } from '#/template/cosnt-enum/CE_TEMPLATE_NAME'; -import { evaluateTemplate } from '#/template/evaluateTemplate'; +import { container } from '#/modules/containers/container'; +import { SymbolTemplateRenderer } from '#/modules/containers/keys/SymbolTemplateRenderer'; +import type { TemplateRenderer } from '#/templates/TemplateRenderer'; +import { CE_TEMPLATE_NAME } from '#/templates/cosnt-enum/CE_TEMPLATE_NAME'; import { getDirname } from 'my-node-fp'; -import path from 'path'; +import pathe from 'pathe'; import type { AsyncReturnType } from 'type-fest'; export async function createMarkdown( option: IBuildCommandOption, renderData: AsyncReturnType, ): Promise { - const rawMarkdown = await evaluateTemplate(CE_TEMPLATE_NAME.MARKDOWN_DOCUMENT, renderData); + const renderer = container.resolve(SymbolTemplateRenderer); + const rawMarkdown = await renderer.evaluate(CE_TEMPLATE_NAME.MARKDOWN_DOCUMENT, renderData); const prettiedMarkdown = await applyPrettier(rawMarkdown, 'md', option.prettierConfig); const markdownFileName = `${renderData.metadata.name}.md`; const outputDir = await getDirname(option.output ?? process.cwd()); return { - filename: path.resolve(path.join(outputDir, markdownFileName)), - dirname: path.resolve(outputDir), + filename: pathe.resolve(pathe.join(outputDir, markdownFileName)), + dirname: pathe.resolve(outputDir), content: prettiedMarkdown, } satisfies IErdiaDocument; } diff --git a/src/creators/createPdfHtml.ts b/src/creators/createPdfHtml.ts index 93a5e7c..40c416b 100644 --- a/src/creators/createPdfHtml.ts +++ b/src/creators/createPdfHtml.ts @@ -2,22 +2,28 @@ import type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOpti import { applyPrettier } from '#/creators/applyPretter'; import type { getRenderData } from '#/creators/getRenderData'; import type { IErdiaDocument } from '#/creators/interfaces/IErdiaDocument'; -import { CE_TEMPLATE_NAME } from '#/template/cosnt-enum/CE_TEMPLATE_NAME'; -import { evaluateTemplate } from '#/template/evaluateTemplate'; +import { container } from '#/modules/containers/container'; +import { SymbolTemplateRenderer } from '#/modules/containers/keys/SymbolTemplateRenderer'; +import { betterMkdir } from '#/modules/files/betterMkdir'; +import type { TemplateRenderer } from '#/templates/TemplateRenderer'; +import { CE_TEMPLATE_NAME } from '#/templates/cosnt-enum/CE_TEMPLATE_NAME'; import { getDirname } from 'my-node-fp'; import { randomUUID } from 'node:crypto'; -import path from 'path'; +import pathe from 'pathe'; import type { AsyncReturnType } from 'type-fest'; export async function createPdfHtml(option: IBuildCommandOption, renderData: AsyncReturnType) { - const rawHtml = await evaluateTemplate(CE_TEMPLATE_NAME.PDF_DOCUMENT, renderData); + const renderer = container.resolve(SymbolTemplateRenderer); + const rawHtml = await renderer.evaluate(CE_TEMPLATE_NAME.PDF_DOCUMENT, renderData); const prettiedHtml = await applyPrettier(rawHtml, 'html', option.prettierConfig); - const outputDir = await getDirname(option.output ?? process.cwd()); - const tempFileName = path.join(outputDir, `${randomUUID()}.html`); + const outputDirPath = option.output != null ? pathe.resolve(option.output) : process.cwd(); + await betterMkdir(outputDirPath); + + const tempFileName = pathe.join(outputDirPath, `${randomUUID()}.html`); return { - dirname: path.resolve(outputDir), + dirname: await getDirname(outputDirPath), content: prettiedHtml, - filename: path.resolve(tempFileName), + filename: pathe.resolve(tempFileName), } satisfies IErdiaDocument; } diff --git a/src/creators/getRenderData.ts b/src/creators/getRenderData.ts index 8d3e65a..ff32b89 100644 --- a/src/creators/getRenderData.ts +++ b/src/creators/getRenderData.ts @@ -9,7 +9,7 @@ import type { IRecordMetadata } from '#/databases/interfaces/IRecordMetadata'; import type { IRelationRecord } from '#/databases/interfaces/IRelationRecord'; import type { IRenderData } from '#/databases/interfaces/IRenderData'; import type { TDatabaseRecord } from '#/databases/interfaces/TDatabaseRecord'; -import { getSlashEndRoutePath } from '#/tools/getSlashEndRoutePath'; +import { getSlashEndRoutePath } from '#/modules/getSlashEndRoutePath'; import alasql from 'alasql'; import { compareVersions } from 'compare-versions'; diff --git a/src/creators/writeToImage.ts b/src/creators/writeToImage.ts index a961e5f..73f30d5 100644 --- a/src/creators/writeToImage.ts +++ b/src/creators/writeToImage.ts @@ -2,12 +2,13 @@ import type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption'; import type { getRenderData } from '#/creators/getRenderData'; import type { IErdiaDocument } from '#/creators/interfaces/IErdiaDocument'; -import { getPuppeteerConfig } from '#/tools/getPuppeteerConfig'; +import { betterMkdir } from '#/modules/files/betterMkdir'; +import { getPuppeteerConfig } from '#/modules/getPuppeteerConfig'; import consola from 'consola'; import del from 'del'; -import fs from 'fs'; import { isError } from 'my-easy-fp'; -import path from 'path'; +import fs from 'node:fs'; +import pathe from 'pathe'; import * as puppeteer from 'puppeteer'; import type { AsyncReturnType } from 'type-fest'; @@ -31,6 +32,7 @@ export async function writeToImage( localBrowser = browser; localPage = page; + await betterMkdir(document.filename); await fs.promises.writeFile(document.filename, document.content); await page.setViewport({ width: option.viewportWidth ?? 1280, height: option.viewportHeight ?? 720 * 2 }); await page.goto(`file://${document.filename}`, puppeteerGotoOption); @@ -54,13 +56,13 @@ export async function writeToImage( throw new Error('invalid image html document template'); } - await fs.promises.writeFile(path.join(document.dirname, `${renderData.metadata.name}.svg`), svg); + await fs.promises.writeFile(pathe.join(document.dirname, `${renderData.metadata.name}.svg`), svg); consola.debug('file write end'); await del(document.filename); consola.info(`Component ER diagram successfully write on ${renderData.metadata.name}.svg`); - return [path.join(document.dirname, `${renderData.metadata.name}.svg`)]; + return [pathe.join(document.dirname, `${renderData.metadata.name}.svg`)]; } // this source code from [mermaid-cli](https://github.com/mermaidjs/mermaid.cli/blob/46185413d75384cd7bceed802d187db6852f5190/index.js#L113-L117) @@ -70,7 +72,7 @@ export async function writeToImage( }); await page.screenshot({ - path: path.join(document.dirname, `${renderData.metadata.name}.png`), + path: pathe.join(document.dirname, `${renderData.metadata.name}.png`), clip, omitBackground: false, }); @@ -80,7 +82,7 @@ export async function writeToImage( await del(document.filename); consola.info(`Component ER diagram successfully write on ${renderData.metadata.name}.png`); - return [path.join(document.dirname, `${renderData.metadata.name}.png`)]; + return [pathe.join(document.dirname, `${renderData.metadata.name}.png`)]; } catch (caught) { const err = isError(caught, new Error('unknown error raised from writeToImage')); diff --git a/src/creators/writeToPdf.ts b/src/creators/writeToPdf.ts index 0e0ade5..721b239 100644 --- a/src/creators/writeToPdf.ts +++ b/src/creators/writeToPdf.ts @@ -1,12 +1,12 @@ import type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption'; import type { getRenderData } from '#/creators/getRenderData'; import type { IErdiaDocument } from '#/creators/interfaces/IErdiaDocument'; -import { getPuppeteerConfig } from '#/tools/getPuppeteerConfig'; +import { getPuppeteerConfig } from '#/modules/getPuppeteerConfig'; import consola from 'consola'; import del from 'del'; import fs from 'fs'; import { isError } from 'my-easy-fp'; -import path from 'path'; +import pathe from 'pathe'; import * as puppeteer from 'puppeteer'; import type { AsyncReturnType } from 'type-fest'; @@ -36,13 +36,13 @@ export async function writeToPdf( await fs.promises.writeFile(document.filename, document.content); await page.goto(`file://${document.filename}`, puppeteerGotoOption); await page.pdf({ - path: path.join(document.dirname, `${renderData.metadata.name}.pdf`), + path: pathe.join(document.dirname, `${renderData.metadata.name}.pdf`), printBackground: option.backgroundColor !== 'transparent', }); await del(document.filename); - return [path.join(document.dirname, `${renderData.metadata.name}.pdf`)]; + return [pathe.join(document.dirname, `${renderData.metadata.name}.pdf`)]; } catch (caught) { const err = isError(caught, new Error('unknown error raised from writeToPdf')); diff --git a/src/databases/__tests__/alasql.test.ts b/src/databases/__tests__/alasql.test.ts index 0a964f3..7c24bfc 100644 --- a/src/databases/__tests__/alasql.test.ts +++ b/src/databases/__tests__/alasql.test.ts @@ -1,7 +1,7 @@ import { CE_CHANGE_KIND } from '#/databases/const-enum/CE_CHANGE_KIND'; import type { TDatabaseRecord } from '#/databases/interfaces/TDatabaseRecord'; import alasql from 'alasql'; -import { describe, test } from 'vitest'; +import { describe, expect, test } from 'vitest'; const db: TDatabaseRecord[] = [ { @@ -20,6 +20,18 @@ const db: TDatabaseRecord[] = [ describe('alasql', () => { test('html.column.test', async () => { const row = await alasql.promise('SELECT * FROM ? WHERE name = ?', [db, 'tbl_license']); - console.log(row); + expect(row).toEqual([ + { + $kind: 'entity', + version: '2022-11-22T11:22:33.333+09:00', + createdAt: '2022-11-22T11:22:33.333+09:00', + updatedAt: '2022-11-22T11:22:33.333+09:00', + entity: 'tbl_license', + name: 'tbl_license', + dbName: 'License', + change: 'none', + hasRelation: false, + }, + ]); }); }); diff --git a/src/databases/flushDatabase.ts b/src/databases/flushDatabase.ts index a586a0a..ddc78c4 100644 --- a/src/databases/flushDatabase.ts +++ b/src/databases/flushDatabase.ts @@ -1,23 +1,23 @@ import { CE_DEFAULT_VALUE } from '#/configs/const-enum/CE_DEFAULT_VALUE'; import type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption'; import type { TDatabaseRecord } from '#/databases/interfaces/TDatabaseRecord'; -import { getOutputDirectory } from '#/tools/files/getOutputDirectory'; -import fs from 'fs'; -import path from 'path'; +import { getOutputDirectory } from '#/modules/files/getOutputDirectory'; +import fs from 'node:fs'; +import pathe from 'pathe'; export async function flushDatabase( option: Pick, records: TDatabaseRecord[], ): Promise { const dirname = await getOutputDirectory({ output: option.databasePath }, process.cwd()); - const filename = path.join(dirname, CE_DEFAULT_VALUE.DATABASE_FILENAME); + const filename = pathe.join(dirname, CE_DEFAULT_VALUE.DATABASE_FILENAME); if (filename == null) { throw new Error(`invalid database name: undefined`); } await fs.promises.writeFile( - path.join(dirname, CE_DEFAULT_VALUE.DATABASE_FILENAME), + pathe.join(dirname, CE_DEFAULT_VALUE.DATABASE_FILENAME), JSON.stringify(records, undefined, 2), ); diff --git a/src/databases/openDatabase.ts b/src/databases/openDatabase.ts index 966d639..60f61ef 100644 --- a/src/databases/openDatabase.ts +++ b/src/databases/openDatabase.ts @@ -1,16 +1,16 @@ import { CE_DEFAULT_VALUE } from '#/configs/const-enum/CE_DEFAULT_VALUE'; import type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption'; import type { TDatabaseRecord } from '#/databases/interfaces/TDatabaseRecord'; -import { getOutputDirectory } from '#/tools/files/getOutputDirectory'; -import fs from 'fs'; +import { getOutputDirectory } from '#/modules/files/getOutputDirectory'; import { parse } from 'jsonc-parser'; import { isFalse } from 'my-easy-fp'; import { exists } from 'my-node-fp'; -import path from 'path'; +import fs from 'node:fs'; +import pathe from 'pathe'; export async function openDatabase(option: Pick): Promise { const dirname = await getOutputDirectory({ output: option.databasePath }, process.cwd()); - const filename = path.join(dirname, CE_DEFAULT_VALUE.DATABASE_FILENAME); + const filename = pathe.join(dirname, CE_DEFAULT_VALUE.DATABASE_FILENAME); if (filename == null) { return []; diff --git a/src/modules/containers/container.ts b/src/modules/containers/container.ts new file mode 100644 index 0000000..d7f614d --- /dev/null +++ b/src/modules/containers/container.ts @@ -0,0 +1,3 @@ +import { createContainer } from 'awilix'; + +export const container = createContainer(); diff --git a/src/modules/containers/keys/SymbolDataSource.ts b/src/modules/containers/keys/SymbolDataSource.ts new file mode 100644 index 0000000..60290b1 --- /dev/null +++ b/src/modules/containers/keys/SymbolDataSource.ts @@ -0,0 +1 @@ +export const SymbolDataSource = Symbol('data-source'); diff --git a/src/modules/containers/keys/SymbolDefaultTemplate.ts b/src/modules/containers/keys/SymbolDefaultTemplate.ts new file mode 100644 index 0000000..b069b43 --- /dev/null +++ b/src/modules/containers/keys/SymbolDefaultTemplate.ts @@ -0,0 +1 @@ +export const SymbolDefaultTemplate = Symbol('default-template'); diff --git a/src/modules/containers/keys/SymbolTemplate.ts b/src/modules/containers/keys/SymbolTemplate.ts new file mode 100644 index 0000000..85c7d8b --- /dev/null +++ b/src/modules/containers/keys/SymbolTemplate.ts @@ -0,0 +1 @@ +export const SymbolTemplate = Symbol('template'); diff --git a/src/modules/containers/keys/SymbolTemplateRenderer.ts b/src/modules/containers/keys/SymbolTemplateRenderer.ts new file mode 100644 index 0000000..debc951 --- /dev/null +++ b/src/modules/containers/keys/SymbolTemplateRenderer.ts @@ -0,0 +1 @@ +export const SymbolTemplateRenderer = Symbol('template-renderer'); diff --git a/src/modules/files/__tests__/better.mkdir.test.ts b/src/modules/files/__tests__/better.mkdir.test.ts new file mode 100644 index 0000000..d291cec --- /dev/null +++ b/src/modules/files/__tests__/better.mkdir.test.ts @@ -0,0 +1,34 @@ +import { betterMkdir } from '#/modules/files/betterMkdir'; +import { existsSync } from 'my-node-fp'; +import fs from 'node:fs'; +import pathe from 'pathe'; +import { sync as rimrafSync } from 'rimraf'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +const testDirPath = pathe.join(process.cwd(), 'test-dir'); + +beforeAll(async () => { + await fs.promises.mkdir(testDirPath, { recursive: true }); +}); + +afterAll(() => { + console.log('>> ', testDirPath); + rimrafSync(testDirPath); +}); + +describe('betterMkdir', () => { + it('non exists directory mkdir', async () => { + await betterMkdir(pathe.join(testDirPath, '11')); + expect(existsSync(pathe.join(testDirPath, '11'))).toBeTruthy(); + }); + + it('already exists directory mkdir', async () => { + await betterMkdir(pathe.join(testDirPath)); + expect(existsSync(pathe.join(testDirPath))).toBeTruthy(); + }); + + it('non exists directory mkdir using filename', async () => { + await betterMkdir(pathe.join(testDirPath, '33', 'test.txt')); + expect(existsSync(pathe.join(testDirPath, '33'))).toBeTruthy(); + }); +}); diff --git a/src/tools/__tests__/file.test.ts b/src/modules/files/__tests__/file.test.ts similarity index 89% rename from src/tools/__tests__/file.test.ts rename to src/modules/files/__tests__/file.test.ts index f798372..fe093b2 100644 --- a/src/tools/__tests__/file.test.ts +++ b/src/modules/files/__tests__/file.test.ts @@ -1,9 +1,9 @@ -import { getOutputDirectory } from '#/tools/files/getOutputDirectory'; -import { getPuppeteerConfig } from '#/tools/getPuppeteerConfig'; -import { getSlashEndRoutePath } from '#/tools/getSlashEndRoutePath'; -import fs from 'fs'; +import { getOutputDirectory } from '#/modules/files/getOutputDirectory'; +import { getPuppeteerConfig } from '#/modules/getPuppeteerConfig'; +import { getSlashEndRoutePath } from '#/modules/getSlashEndRoutePath'; import * as mnf from 'my-node-fp'; -import path from 'path'; +import fs from 'node:fs'; +import pathe from 'pathe'; import { describe, expect, test, vitest } from 'vitest'; vitest.mock('my-node-fp', async (importOriginal) => { @@ -36,7 +36,7 @@ describe('getOutputDirectory', () => { existsSpyOn.mockRestore(); mkdirSpyOn.mockRestore(); - expect(p).toEqual(path.join(process.cwd(), 'i-am-cwd')); + expect(p).toEqual(pathe.join(process.cwd(), 'i-am-cwd')); }); test('pass - current directory', async () => { @@ -50,7 +50,7 @@ describe('getOutputDirectory', () => { isDirectorySpyOn.mockRestore(); mkdirSpyOn.mockRestore(); - expect(p).toEqual(path.join(process.cwd(), 'examples')); + expect(p).toEqual(pathe.join(process.cwd(), 'examples')); }); test('pass - cwd directory', async () => { @@ -64,7 +64,7 @@ describe('getOutputDirectory', () => { isDirectorySpyOn.mockRestore(); mkdirSpyOn.mockRestore(); - expect(p).toEqual(path.join(process.cwd(), 'examples')); + expect(p).toEqual(pathe.join(process.cwd(), 'examples')); }); }); diff --git a/src/modules/files/__tests__/glob.test.ts b/src/modules/files/__tests__/glob.test.ts new file mode 100644 index 0000000..7ecc3bb --- /dev/null +++ b/src/modules/files/__tests__/glob.test.ts @@ -0,0 +1,53 @@ +import { getGlobFiles } from '#/modules/files/getGlobFiles'; +import { defaultExclude } from '#/modules/scopes/defaultExclude'; +import { Glob } from 'glob'; +import pathe from 'pathe'; +import { describe, expect, it } from 'vitest'; + +describe('getGlobFiles', () => { + it('string type search result', () => { + const resolvedTemplatePath = pathe.join(process.cwd(), 'templates', 'html'); + const globs = new Glob(pathe.join(resolvedTemplatePath, `**`, '*.eta'), { + absolute: true, + ignore: defaultExclude, + cwd: resolvedTemplatePath, + windowsPathsNoEscape: true, + }); + + const files = getGlobFiles(globs); + + expect(files).toEqual([ + pathe.join(process.cwd(), 'templates/html/table.eta'), + pathe.join(process.cwd(), 'templates/html/style.eta'), + pathe.join(process.cwd(), 'templates/html/mermaid.eta'), + pathe.join(process.cwd(), 'templates/html/mermaid-toc.eta'), + pathe.join(process.cwd(), 'templates/html/mermaid-diagram.eta'), + pathe.join(process.cwd(), 'templates/html/document.eta'), + pathe.join(process.cwd(), 'templates/html/document-toc.eta'), + ]); + }); + + it('stat type search result', () => { + const resolvedTemplatePath = pathe.join(process.cwd(), 'templates', 'html'); + const globs = new Glob(pathe.join(resolvedTemplatePath, `**`, '*.eta'), { + ignore: defaultExclude, + cwd: resolvedTemplatePath, + windowsPathsNoEscape: true, + // stat, withFileTypes option both set `true`, node-glob return Path object + stat: true, + withFileTypes: true, + }); + + const files = getGlobFiles(globs); + + expect(files).toEqual([ + pathe.join(process.cwd(), 'templates/html/table.eta'), + pathe.join(process.cwd(), 'templates/html/style.eta'), + pathe.join(process.cwd(), 'templates/html/mermaid.eta'), + pathe.join(process.cwd(), 'templates/html/mermaid-toc.eta'), + pathe.join(process.cwd(), 'templates/html/mermaid-diagram.eta'), + pathe.join(process.cwd(), 'templates/html/document.eta'), + pathe.join(process.cwd(), 'templates/html/document-toc.eta'), + ]); + }); +}); diff --git a/src/modules/files/betterMkdir.ts b/src/modules/files/betterMkdir.ts new file mode 100644 index 0000000..fb87c92 --- /dev/null +++ b/src/modules/files/betterMkdir.ts @@ -0,0 +1,20 @@ +import { isFalse } from 'my-easy-fp'; +import { exists, getDirname } from 'my-node-fp'; +import fs from 'node:fs'; +import pathe from 'pathe'; + +export async function betterMkdir(filePath: string) { + const isFilePathExist = await exists(filePath); + + if (isFalse(isFilePathExist)) { + const extname = pathe.extname(filePath); + const hasExtname = extname !== '' && extname.length > 0; + + if (hasExtname) { + const dirPath = await getDirname(filePath); + await fs.promises.mkdir(dirPath, { recursive: true }); + } else { + await fs.promises.mkdir(filePath, { recursive: true }); + } + } +} diff --git a/src/tools/files/getFindFile.ts b/src/modules/files/getFindFile.ts similarity index 100% rename from src/tools/files/getFindFile.ts rename to src/modules/files/getFindFile.ts diff --git a/src/modules/files/getGlobFiles.ts b/src/modules/files/getGlobFiles.ts new file mode 100644 index 0000000..ef0682c --- /dev/null +++ b/src/modules/files/getGlobFiles.ts @@ -0,0 +1,12 @@ +import type { Glob, GlobOptions } from 'glob'; +import pathe from 'pathe'; + +export function getGlobFiles(glob: Glob): string[] { + const filePathSet = new Set(); + + for (const filePath of glob) { + filePathSet.add(typeof filePath === 'string' ? filePath : pathe.join(filePath.path, filePath.name)); + } + + return Array.from(filePathSet); +} diff --git a/src/tools/files/getOutputDirectory.ts b/src/modules/files/getOutputDirectory.ts similarity index 64% rename from src/tools/files/getOutputDirectory.ts rename to src/modules/files/getOutputDirectory.ts index 7058caf..f0edd84 100644 --- a/src/tools/files/getOutputDirectory.ts +++ b/src/modules/files/getOutputDirectory.ts @@ -1,20 +1,20 @@ import type { IBuildCommandOption } from '#/configs/interfaces/IBuildCommandOption'; -import fs from 'fs'; +import { betterMkdir } from '#/modules/files/betterMkdir'; import { isFalse } from 'my-easy-fp'; import { exists, getDirname, isDirectory } from 'my-node-fp'; -import path from 'path'; +import pathe from 'pathe'; export async function getOutputDirectory(option: Pick, cwd: string) { const output = option.output ?? cwd; if (isFalse(await exists(output))) { - await fs.promises.mkdir(output, { recursive: true }); - return path.resolve(output); + await betterMkdir(output); + return pathe.resolve(output); } if (isFalse(await isDirectory(output))) { - return path.resolve(await getDirname(output)); + return pathe.resolve(await getDirname(output)); } - return path.resolve(output); + return pathe.resolve(output); } diff --git a/src/tools/getPuppeteerConfig.ts b/src/modules/getPuppeteerConfig.ts similarity index 100% rename from src/tools/getPuppeteerConfig.ts rename to src/modules/getPuppeteerConfig.ts diff --git a/src/tools/getSlashEndRoutePath.ts b/src/modules/getSlashEndRoutePath.ts similarity index 100% rename from src/tools/getSlashEndRoutePath.ts rename to src/modules/getSlashEndRoutePath.ts diff --git a/src/modules/scopes/defaultExclude.ts b/src/modules/scopes/defaultExclude.ts new file mode 100644 index 0000000..922a1bc --- /dev/null +++ b/src/modules/scopes/defaultExclude.ts @@ -0,0 +1 @@ +export const defaultExclude = ['node_modules/**', 'flow-typed/**', 'coverage/**', '.git/**']; diff --git a/src/template/cosnt-enum/CE_TEMPLATE_NAME.ts b/src/template/cosnt-enum/CE_TEMPLATE_NAME.ts deleted file mode 100644 index 62bfdbe..0000000 --- a/src/template/cosnt-enum/CE_TEMPLATE_NAME.ts +++ /dev/null @@ -1,32 +0,0 @@ -export const CE_TEMPLATE_NAME = { - HTML_DOCUMENT_TOC: 'html/document-toc', - HTML_DOCUMENT: 'html/document', - HTML_MERMAID_SCRIPT: 'html/mermaid-script', - HTML_MERMAID_TOC: 'html/mermaid-toc', - HTML_MERMAID: 'html/mermaid', - HTML_STYLE: 'html/style', - HTML_TABLE: 'html/table', - - IMAGE_DOCUMENT: 'image/document', - IMAGE_MERMAID_SCRIPT: 'image/mermaid-script', - IMAGE_STYLE: 'image/style', - - MARKDOWN_DOCUMENT: 'markdown/document', - MARKDOWN_MERMAID: 'markdown/mermaid', - MARKDOWN_TABLE: 'markdown/table', - MARKDOWN_TOC: 'markdown/toc', - - MERMAID_DOCUMENT: 'mermaid/document', - MERMAID_ENTITY: 'mermaid/entity', - MERMAID_RELATION: 'mermaid/relation', - - PDF_DOCUMENT_TOC: 'pdf/document-toc', - PDF_DOCUMENT: 'pdf/document', - PDF_MERMAID_SCRIPT: 'pdf/mermaid-script', - PDF_STYLE: 'pdf/style', - PDF_TABLE: 'pdf/table', - - CONFIG_JSON: 'config/json', -} as const; - -export type CE_TEMPLATE_NAME = (typeof CE_TEMPLATE_NAME)[keyof typeof CE_TEMPLATE_NAME]; diff --git a/src/template/defaultTemplates.ts b/src/template/defaultTemplates.ts deleted file mode 100644 index 54a6214..0000000 --- a/src/template/defaultTemplates.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { build as buildConfig } from '#/template/config/init-command'; -import { CE_TEMPLATE_NAME } from '#/template/cosnt-enum/CE_TEMPLATE_NAME'; -import { htmlDocument } from '#/template/html/document'; -import { documentToC as htmlToC } from '#/template/html/document-toc'; -import { mermaid as htmlMermaid } from '#/template/html/mermaid'; -import { mermaidScript } from '#/template/html/mermaid-script'; -import { documentToC as mermaidToC } from '#/template/html/mermaid-toc'; -import { style as htmlStyle } from '#/template/html/style'; -import { htmlTable } from '#/template/html/table'; -import { htmlDocument as imageDocument } from '#/template/image/document'; -import { mermaidScript as imageMermaidScript } from '#/template/image/mermaid-script'; -import { style as imageStyle } from '#/template/image/style'; -import { markdownDocument } from '#/template/markdown/document'; -import { mermaid as markdownMermaid } from '#/template/markdown/mermaid'; -import { table as markdownTable } from '#/template/markdown/table'; -import { toc as markdownToC } from '#/template/markdown/toc'; -import { mermaidDocument } from '#/template/mermaid/document'; -import { entity as mermaidEntity } from '#/template/mermaid/entity'; -import { relation as mermaidRelation } from '#/template/mermaid/relation'; -import { htmlDocument as pdfDocument } from '#/template/pdf/document'; -import { documentToC as pdfToC } from '#/template/pdf/document-toc'; -import { mermaidScript as pdfMermaidScript } from '#/template/pdf/mermaid-script'; -import { style as pdfStyle } from '#/template/pdf/style'; -import { htmlTable as pdfTable } from '#/template/pdf/table'; - -export const defaultTemplates: Record = { - [CE_TEMPLATE_NAME.HTML_DOCUMENT_TOC]: htmlToC, - [CE_TEMPLATE_NAME.HTML_DOCUMENT]: htmlDocument, - [CE_TEMPLATE_NAME.HTML_MERMAID_SCRIPT]: mermaidScript, - [CE_TEMPLATE_NAME.HTML_MERMAID_TOC]: mermaidToC, - [CE_TEMPLATE_NAME.HTML_MERMAID]: htmlMermaid, - [CE_TEMPLATE_NAME.HTML_STYLE]: htmlStyle, - [CE_TEMPLATE_NAME.HTML_TABLE]: htmlTable, - - [CE_TEMPLATE_NAME.IMAGE_DOCUMENT]: imageDocument, - [CE_TEMPLATE_NAME.IMAGE_MERMAID_SCRIPT]: imageMermaidScript, - [CE_TEMPLATE_NAME.IMAGE_STYLE]: imageStyle, - - [CE_TEMPLATE_NAME.MARKDOWN_DOCUMENT]: markdownDocument, - [CE_TEMPLATE_NAME.MARKDOWN_MERMAID]: markdownMermaid, - [CE_TEMPLATE_NAME.MARKDOWN_TABLE]: markdownTable, - [CE_TEMPLATE_NAME.MARKDOWN_TOC]: markdownToC, - - [CE_TEMPLATE_NAME.MERMAID_DOCUMENT]: mermaidDocument, - [CE_TEMPLATE_NAME.MERMAID_ENTITY]: mermaidEntity, - [CE_TEMPLATE_NAME.MERMAID_RELATION]: mermaidRelation, - - [CE_TEMPLATE_NAME.PDF_DOCUMENT_TOC]: pdfToC, - [CE_TEMPLATE_NAME.PDF_DOCUMENT]: pdfDocument, - [CE_TEMPLATE_NAME.PDF_MERMAID_SCRIPT]: pdfMermaidScript, - [CE_TEMPLATE_NAME.PDF_STYLE]: pdfStyle, - [CE_TEMPLATE_NAME.PDF_TABLE]: pdfTable, - - [CE_TEMPLATE_NAME.CONFIG_JSON]: buildConfig, -}; diff --git a/src/template/evaluateTemplate.ts b/src/template/evaluateTemplate.ts deleted file mode 100644 index d4e5cb9..0000000 --- a/src/template/evaluateTemplate.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { getTemplates } from '#/template/loadTemplates'; -import consola from 'consola'; -import { Eta } from 'eta'; -import { isError } from 'my-easy-fp'; - -const eta = new Eta({ views: 'erdia' }); -eta.resolvePath = (templatePath: string) => templatePath; -eta.readFile = (templatePath: string) => getTemplates()[templatePath]; - -export async function evaluateTemplate(name: string, data: T) { - try { - const rendered = eta.render(name, data); - return rendered; - } catch (caught) { - const err = isError(caught, new Error('raise error from evaluateTemplate')); - consola.error(`template: ${name}`, data); - consola.error(err); - throw err; - } -} diff --git a/src/template/html/mermaid-script.ts b/src/template/html/mermaid-script.ts deleted file mode 100644 index f030d8f..0000000 --- a/src/template/html/mermaid-script.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const mermaidScript = ` -`; diff --git a/src/template/html/style.ts b/src/template/html/style.ts deleted file mode 100644 index a52a61b..0000000 --- a/src/template/html/style.ts +++ /dev/null @@ -1,149 +0,0 @@ -export const style = ``; diff --git a/src/template/image/mermaid-script.ts b/src/template/image/mermaid-script.ts deleted file mode 100644 index c6ad505..0000000 --- a/src/template/image/mermaid-script.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const mermaidScript = ` - `; diff --git a/src/template/image/style.ts b/src/template/image/style.ts deleted file mode 100644 index 7730099..0000000 --- a/src/template/image/style.ts +++ /dev/null @@ -1,56 +0,0 @@ -export const style = ``; diff --git a/src/template/loadTemplates.ts b/src/template/loadTemplates.ts deleted file mode 100644 index 55953af..0000000 --- a/src/template/loadTemplates.ts +++ /dev/null @@ -1,94 +0,0 @@ -import type { IDocumentOption } from '#/configs/interfaces/IDocumentOption'; -import { defaultTemplates } from '#/template/defaultTemplates'; -import fs from 'fs'; -import globby from 'globby'; -import { isTrue } from 'my-easy-fp'; -import { exists } from 'my-node-fp'; -import path from 'node:path'; - -const templates: Record = { ...defaultTemplates }; - -export function getTemplate(template: string) { - return templates[template]; -} - -export function getTemplates() { - return templates; -} - -export async function loadTemplates(option: Pick) { - const { templatePath } = option; - - if (templatePath == null) { - return true; - } - - const resolvedTemplateFilePath = path.resolve(templatePath); - const [htmlTemplateFiles, markdownTemplateFiles, imageTemplateFiles, pdfTemplateFiles, mermaidTemplateFiles] = - await Promise.all([ - globby(['**/*'], { - cwd: path.join(resolvedTemplateFilePath, 'html'), - onlyFiles: true, - gitignore: true, - dot: true, - }), - - globby(['**/*'], { - cwd: path.join(resolvedTemplateFilePath, 'markdown'), - onlyFiles: true, - gitignore: true, - dot: true, - }), - - globby(['**/*'], { - cwd: path.join(resolvedTemplateFilePath, 'image'), - onlyFiles: true, - gitignore: true, - dot: true, - }), - - globby(['**/*'], { - cwd: path.join(resolvedTemplateFilePath, 'pdf'), - onlyFiles: true, - gitignore: true, - dot: true, - }), - - globby(['**/*'], { - cwd: path.join(resolvedTemplateFilePath, 'mermaid'), - onlyFiles: true, - gitignore: true, - dot: true, - }), - ]); - - const templateFiles = [ - htmlTemplateFiles.map((filePath) => path.join(resolvedTemplateFilePath, 'html', filePath)), - markdownTemplateFiles.map((filePath) => path.join(resolvedTemplateFilePath, 'markdown', filePath)), - imageTemplateFiles.map((filePath) => path.join(resolvedTemplateFilePath, 'image', filePath)), - pdfTemplateFiles.map((filePath) => path.join(resolvedTemplateFilePath, 'pdf', filePath)), - mermaidTemplateFiles.map((filePath) => path.join(resolvedTemplateFilePath, 'mermaid', filePath)), - ].flat(); - - const loadedTemplateFiles = ( - await Promise.all( - templateFiles.map(async (templateFilePath) => { - if (isTrue(await exists(templateFilePath))) { - const buf = await fs.promises.readFile(templateFilePath); - const relative = path.relative(resolvedTemplateFilePath, templateFilePath).replace(`.${path.sep}`, ''); - templates[relative] = buf.toString(); - - return { key: relative, content: buf.toString() }; - } - - return undefined; - }), - ) - ).filter((template): template is { key: string; content: string } => template != null); - - loadedTemplateFiles.forEach((templateFile) => { - templates[templateFile.key] = templateFile.content; - }); - - return true; -} diff --git a/src/template/markdown/document.ts b/src/template/markdown/document.ts deleted file mode 100644 index b903689..0000000 --- a/src/template/markdown/document.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { CE_OUTPUT_COMPONENT } from '#/configs/const-enum/CE_OUTPUT_COMPONENT'; -import { CE_TEMPLATE_NAME } from '#/template/cosnt-enum/CE_TEMPLATE_NAME'; - -export const markdownDocument = `# <%= it.metadata.name %> - -<% it.versions.filter((version) => version.latest).forEach((version) => { %> -<% if (it.option.components.includes('${CE_OUTPUT_COMPONENT.TABLE}')) { %> -<%~ include('${CE_TEMPLATE_NAME.MARKDOWN_TOC}', { entities: version.entities, option: it.option, metadata: it.metadata }); %> -<%~ include('${CE_TEMPLATE_NAME.MARKDOWN_TABLE}', { entities: version.entities, option: it.option, metadata: it.metadata }); %> -<% } %> - -<% if (it.option.components.includes('${CE_OUTPUT_COMPONENT.ER}')) { %> -<%~ include('${CE_TEMPLATE_NAME.MARKDOWN_MERMAID}', { entities: version.entities, option: it.option, metadata: it.metadata }); %> -<% } %> -<% }) %> -`; diff --git a/src/template/markdown/mermaid.ts b/src/template/markdown/mermaid.ts deleted file mode 100644 index bab084c..0000000 --- a/src/template/markdown/mermaid.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { CE_TEMPLATE_NAME } from '#/template/cosnt-enum/CE_TEMPLATE_NAME'; - -export const mermaid = `## ER Diagram - -\`\`\`mermaid -<%~ include('${CE_TEMPLATE_NAME.MERMAID_DOCUMENT}', { entities: it.entities, option: it.option, metadata: it.metadata }); -%> -\`\`\``; diff --git a/src/template/markdown/toc.ts b/src/template/markdown/toc.ts deleted file mode 100644 index 800a19e..0000000 --- a/src/template/markdown/toc.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { CE_OUTPUT_COMPONENT } from '#/configs/const-enum/CE_OUTPUT_COMPONENT'; - -export const toc = `## Table Of Contents - -1. <%= it.metadata.version %> Entities -<% it.entities.forEach((entity, index) => { -%> - <%= index + 1 %>. <%= entity.dbName %>(<%= entity.name %>) -<% }) %> -<% if (it.option.components.includes('${CE_OUTPUT_COMPONENT.ER}')) { %> -2. ER Diagram -<% } %> -`; diff --git a/src/template/mermaid/document.ts b/src/template/mermaid/document.ts deleted file mode 100644 index 3bc1949..0000000 --- a/src/template/mermaid/document.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { CE_TEMPLATE_NAME } from '#/template/cosnt-enum/CE_TEMPLATE_NAME'; - -export const mermaidDocument = `%%{init: {'theme':'<%= it.option.theme %>'}}%% - -erDiagram - -<% it.entities.forEach((entity) => { -%> -<%~ include('${CE_TEMPLATE_NAME.MERMAID_ENTITY}', { entity }) %> -<% -%> -<%~ include('${CE_TEMPLATE_NAME.MERMAID_RELATION}', { entity }) %> - -<% }) -%>`; diff --git a/src/template/mermaid/entity.ts b/src/template/mermaid/entity.ts deleted file mode 100644 index 5c69b4e..0000000 --- a/src/template/mermaid/entity.ts +++ /dev/null @@ -1,13 +0,0 @@ -export const entity = `"<%= it.entity.dbName %>(<%= it.entity.name %>)" { -<% it.entity.columns.forEach((column) => { %> - <%= column.columnTypeWithLength %> <%= column.name -%> - <% if (column.attributeKey != null && column.attributeKey.length > 0) { %> - <%= column.attributeKey.join(',') -%> - <% } %> - <% if (column.comment != null && column.comment != '') { %> - "<%= column.comment -%>"<% -%> - <% } %> - -<% }) %> -} -`; diff --git a/src/template/pdf/mermaid-script.ts b/src/template/pdf/mermaid-script.ts deleted file mode 100644 index c6ad505..0000000 --- a/src/template/pdf/mermaid-script.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const mermaidScript = ` - `; diff --git a/src/template/pdf/style.ts b/src/template/pdf/style.ts deleted file mode 100644 index 12190c1..0000000 --- a/src/template/pdf/style.ts +++ /dev/null @@ -1,60 +0,0 @@ -export const style = ``; diff --git a/src/templates/TemplateRenderer.ts b/src/templates/TemplateRenderer.ts new file mode 100644 index 0000000..5b8e3eb --- /dev/null +++ b/src/templates/TemplateRenderer.ts @@ -0,0 +1,35 @@ +import consola from 'consola'; +import { Eta } from 'eta'; +import { isError, orThrow } from 'my-easy-fp'; + +export class TemplateRenderer { + #eta: Eta; + + #templates: Map; + + #defaultTemplates: Map; + + constructor(templates: Map, defaultTemplates: Map) { + this.#templates = templates; + this.#defaultTemplates = defaultTemplates; + + this.#eta = new Eta({ views: 'erdia', autoEscape: false }); + this.#eta.resolvePath = (templatePath: string) => templatePath; + this.#eta.readFile = (templatePath: string) => { + const template = this.#templates.get(templatePath) ?? this.#defaultTemplates.get(templatePath); + return orThrow(template, new Error(`cannot found template: ${templatePath}`)); + }; + } + + async evaluate(name: string, data: T) { + try { + const rendered = this.#eta.render(name, data); + return rendered; + } catch (caught) { + const err = isError(caught, new Error(`raise error from evaluateTemplate: ${name}`)); + consola.error(`template: ${name}`, data); + consola.error(err); + throw err; + } + } +} diff --git a/src/templates/cosnt-enum/CE_TEMPLATE_NAME.ts b/src/templates/cosnt-enum/CE_TEMPLATE_NAME.ts new file mode 100644 index 0000000..c6568ab --- /dev/null +++ b/src/templates/cosnt-enum/CE_TEMPLATE_NAME.ts @@ -0,0 +1,28 @@ +export const CE_TEMPLATE_NAME = { + HTML_DOCUMENT_TOC: 'html-document-toc', + HTML_DOCUMENT: 'html-document', + HTML_MERMAID: 'html-mermaid', + HTML_MERMAID_TOC: 'html-mermaid-toc', + HTML_MERMAID_DIAGRAM: 'html-mermaid-diagram', + HTML_STYLE: 'html-style', + HTML_TABLE: 'html-table', + + IMAGE_DOCUMENT: 'image-document', + IMAGE_MERMAID_DIAGRAM: 'image-mermaid-diagram', + IMAGE_STYLE: 'image-style', + + MARKDOWN_DOCUMENT: 'markdown-document', + MARKDOWN_MERMAID_DIAGRAM: 'markdown-mermaid-diagram', + MARKDOWN_TABLE: 'markdown-table', + MARKDOWN_TOC: 'markdown-toc', + + PDF_DOCUMENT_TOC: 'pdf-document-toc', + PDF_DOCUMENT: 'pdf-document', + PDF_MERMAID_DIAGRAM: 'pdf-mermaid-diagram', + PDF_STYLE: 'pdf-style', + PDF_TABLE: 'pdf-table', + + CONFIG_JSON: 'config-json', +} as const; + +export type CE_TEMPLATE_NAME = (typeof CE_TEMPLATE_NAME)[keyof typeof CE_TEMPLATE_NAME]; diff --git a/src/templates/interfaces/ITemplate.ts b/src/templates/interfaces/ITemplate.ts new file mode 100644 index 0000000..fc9b563 --- /dev/null +++ b/src/templates/interfaces/ITemplate.ts @@ -0,0 +1,4 @@ +export interface ITemplate { + key: string; + content: string; +} diff --git a/src/templates/modules/__tests__/load.template.test.ts b/src/templates/modules/__tests__/load.template.test.ts new file mode 100644 index 0000000..b761a7e --- /dev/null +++ b/src/templates/modules/__tests__/load.template.test.ts @@ -0,0 +1,17 @@ +import { loadTemplates } from '#/templates/modules/loadTemplates'; +import pathe from 'pathe'; +import { describe, expect, it } from 'vitest'; + +const templateDirPath = pathe.join(process.cwd(), 'templates'); + +describe('loadTemplates', () => { + it('successfully template files loading', async () => { + const template = await loadTemplates({ templatePath: templateDirPath }); + expect(template.default.size).toEqual(template.template.size); + }); + + it('not set template files directory', async () => { + const template = await loadTemplates({ templatePath: undefined }); + expect(template.default.size).toEqual(template.template.size); + }); +}); diff --git a/src/templates/modules/__tests__/template.path.test.ts b/src/templates/modules/__tests__/template.path.test.ts new file mode 100644 index 0000000..7352e31 --- /dev/null +++ b/src/templates/modules/__tests__/template.path.test.ts @@ -0,0 +1,58 @@ +import { getTemplatePath } from '#/templates/modules/getTemplatePath'; +import * as mnf from 'my-node-fp'; +import pathe from 'pathe'; +import { describe, expect, it, vitest } from 'vitest'; + +vitest.mock('my-node-fp', async (importOriginal) => { + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const mod = await importOriginal(); + return { + ...mod, + }; +}); + +describe('getTemplatePath', () => { + it('cannot template path', async () => { + const handle = vitest + .spyOn(mnf, 'exists') + .mockImplementationOnce(() => Promise.resolve(false)) + .mockImplementationOnce(() => Promise.resolve(false)); + + await expect(async () => { + return getTemplatePath(); + }).rejects.toThrowError(); + + handle.mockRestore(); + }); + + it('template path based on cwd', async () => { + const cwdTemplatePath = pathe.join(process.cwd(), 'templates'); + const templatePath = await getTemplatePath(cwdTemplatePath); + expect(templatePath).toEqual(cwdTemplatePath); + }); + + it('template path based on __dirname', async () => { + const dirnameTemplatePath = pathe.join(process.cwd(), 'src', 'templates', 'modules', '__tests__'); + const templatePath = await getTemplatePath('__tests__'); + expect(templatePath).toEqual(dirnameTemplatePath); + }); + + it('template path based on 3 step parent directory', async () => { + const dirnameTemplatePath = pathe.join(process.cwd(), 'templates'); + const templatePath = await getTemplatePath('1110a038cb804e8fac8161070a601f66'); + expect(templatePath).toEqual(dirnameTemplatePath); + }); + + it('template path based on 1 step parent directory, in distribution directory', async () => { + vitest + .spyOn(mnf, 'exists') + .mockImplementationOnce(() => Promise.resolve(false)) + .mockImplementationOnce(() => Promise.resolve(false)) + .mockImplementationOnce(() => Promise.resolve(false)) + .mockImplementationOnce(() => Promise.resolve(true)); + + const dirnameTemplatePath = pathe.join(process.cwd(), 'src', 'templates', 'templates'); + const templatePath = await getTemplatePath('1110a038cb804e8fac8161070a601f66'); + expect(templatePath).toEqual(dirnameTemplatePath); + }); +}); diff --git a/src/templates/modules/__tests__/template.test.ts b/src/templates/modules/__tests__/template.test.ts new file mode 100644 index 0000000..2f3c3f4 --- /dev/null +++ b/src/templates/modules/__tests__/template.test.ts @@ -0,0 +1,43 @@ +import { getTemplate } from '#/templates/modules/getTemplate'; +import { getTemplates } from '#/templates/modules/getTemplates'; +import pathe from 'pathe'; +import { describe, expect, it } from 'vitest'; + +const templateDirPath = pathe.join(process.cwd(), 'templates'); + +describe('getTemplate', () => { + it('successfully template loading', async () => { + const templateHTMLPath = pathe.join(templateDirPath, 'html'); + const template = await getTemplate(templateHTMLPath, pathe.join(templateHTMLPath, 'document.eta')); + + expect(template).toBeDefined(); + expect(template?.key).toBe('document'); + }); + + it('failed template loading', async () => { + const templateCategoryPath = pathe.join(templateDirPath, 'html'); + const template = await getTemplate( + templateCategoryPath, + pathe.join(templateCategoryPath, 'cannot-found-this-template.eta'), + ); + + expect(template).toBeUndefined(); + }); +}); + +describe('getTemplates', () => { + it('successfully loading html templates', async () => { + const templateHTMLPath = pathe.join(templateDirPath, 'html'); + const templates = await getTemplates(templateHTMLPath); + + expect(templates.map((template) => template.key)).toEqual([ + 'table', + 'style', + 'mermaid', + 'mermaid-toc', + 'mermaid-diagram', + 'document', + 'document-toc', + ]); + }); +}); diff --git a/src/templates/modules/getTemplate.ts b/src/templates/modules/getTemplate.ts new file mode 100644 index 0000000..79faa66 --- /dev/null +++ b/src/templates/modules/getTemplate.ts @@ -0,0 +1,18 @@ +import type { ITemplate } from '#/templates/interfaces/ITemplate'; +import { isTrue } from 'my-easy-fp'; +import { basenames, exists, getDirname } from 'my-node-fp'; +import fs from 'node:fs'; +import pathe from 'pathe'; + +export async function getTemplate(dirPath: string, filePath: string): Promise { + if (isTrue(await exists(filePath))) { + const buf = await fs.promises.readFile(filePath); + const relative = pathe.relative(dirPath, filePath).replace(`.${pathe.sep}`, ''); + const dirname = await getDirname(relative); + const basename = basenames(relative, ['.eta', '.ejs']); + + return { key: pathe.join(dirname, basename), content: buf.toString() }; + } + + return undefined; +} diff --git a/src/templates/modules/getTemplatePath.ts b/src/templates/modules/getTemplatePath.ts new file mode 100644 index 0000000..4c5a1a5 --- /dev/null +++ b/src/templates/modules/getTemplatePath.ts @@ -0,0 +1,33 @@ +import { CE_DEFAULT_VALUE } from '#/configs/const-enum/CE_DEFAULT_VALUE'; +import { exists } from 'my-node-fp'; +import pathe from 'pathe'; + +export async function getTemplatePath(templatePathParam?: string): Promise { + if (templatePathParam != null && (await exists(pathe.resolve(templatePathParam)))) { + return pathe.resolve(templatePathParam); + } + + const currentFilePath = pathe.resolve(__dirname); + + if (templatePathParam != null) { + const currentWithTemplatePath = pathe.resolve(pathe.join(currentFilePath, templatePathParam)); + if (await exists(currentWithTemplatePath)) { + return currentWithTemplatePath; + } + } + + const packageRootTemplatePath = pathe.resolve( + pathe.join(currentFilePath, '..', '..', '..', CE_DEFAULT_VALUE.TEMPLATES_PATH), + ); + + if (await exists(packageRootTemplatePath)) { + return packageRootTemplatePath; + } + + const distTemplatePath = pathe.resolve(pathe.join(currentFilePath, '..', CE_DEFAULT_VALUE.TEMPLATES_PATH)); + if (await exists(distTemplatePath)) { + return distTemplatePath; + } + + throw new Error('cannot found template directory!'); +} diff --git a/src/templates/modules/getTemplates.ts b/src/templates/modules/getTemplates.ts new file mode 100644 index 0000000..0416502 --- /dev/null +++ b/src/templates/modules/getTemplates.ts @@ -0,0 +1,28 @@ +import { getGlobFiles } from '#/modules/files/getGlobFiles'; +import { defaultExclude } from '#/modules/scopes/defaultExclude'; +import type { ITemplate } from '#/templates/interfaces/ITemplate'; +import { getTemplate } from '#/templates/modules/getTemplate'; +import { Glob, type GlobOptions } from 'glob'; +import pathe from 'pathe'; + +export async function getTemplates(templatePath: string, globOptions?: GlobOptions) { + const resolvedTemplatePath = pathe.resolve(templatePath); + + const globs = new Glob(pathe.join(resolvedTemplatePath, `**`, '*.eta'), { + ...globOptions, + absolute: true, + ignore: defaultExclude, + cwd: resolvedTemplatePath, + windowsPathsNoEscape: true, + }); + + const templateFilePaths = getGlobFiles(globs) + .map((filePath): [string, boolean] => [filePath, true]) + .map(([filePath, _flag]) => filePath); + + const loadedTemplateFiles = ( + await Promise.all(templateFilePaths.map((templateFilePath) => getTemplate(resolvedTemplatePath, templateFilePath))) + ).filter((template): template is ITemplate => template != null); + + return loadedTemplateFiles; +} diff --git a/src/templates/modules/loadTemplates.ts b/src/templates/modules/loadTemplates.ts new file mode 100644 index 0000000..1a2fb72 --- /dev/null +++ b/src/templates/modules/loadTemplates.ts @@ -0,0 +1,52 @@ +import { CE_DEFAULT_VALUE } from '#/configs/const-enum/CE_DEFAULT_VALUE'; +import type { IDocumentOption } from '#/configs/interfaces/IDocumentOption'; +import { getTemplatePath } from '#/templates/modules/getTemplatePath'; +import { getTemplates } from '#/templates/modules/getTemplates'; +import pathe from 'pathe'; + +export async function loadTemplates(option: Pick) { + const defaultTemplatePath = await getTemplatePath(CE_DEFAULT_VALUE.TEMPLATES_PATH); + const [defaultConfig, defaultHtml, defaultMarkdown, defaultImage, defaultPdf] = await Promise.all([ + getTemplates(pathe.join(defaultTemplatePath, 'config'), {}), + getTemplates(pathe.join(defaultTemplatePath, 'html'), {}), + getTemplates(pathe.join(defaultTemplatePath, 'markdown'), {}), + getTemplates(pathe.join(defaultTemplatePath, 'image'), {}), + getTemplates(pathe.join(defaultTemplatePath, 'pdf'), {}), + ]); + + const defaultTemplateMap = new Map([ + ...defaultConfig.map((template): [string, string] => [`config-${template.key}`, template.content]), + ...defaultHtml.map((template): [string, string] => [`html-${template.key}`, template.content]), + ...defaultMarkdown.map((template): [string, string] => [`markdown-${template.key}`, template.content]), + ...defaultImage.map((template): [string, string] => [`image-${template.key}`, template.content]), + ...defaultPdf.map((template): [string, string] => [`pdf-${template.key}`, template.content]), + ]); + + if (option.templatePath == null) { + return { + default: defaultTemplateMap, + template: defaultTemplateMap, + }; + } + + const templatePath = await getTemplatePath(option.templatePath); + const [templateHtml, templateMarkdown, templateImage, templatePdf] = await Promise.all([ + getTemplates(pathe.join(templatePath, 'html'), {}), + getTemplates(pathe.join(templatePath, 'markdown'), {}), + getTemplates(pathe.join(templatePath, 'image'), {}), + getTemplates(pathe.join(templatePath, 'pdf'), {}), + ]); + + const templateMap = new Map([ + ...defaultConfig.map((template): [string, string] => [`config-${template.key}`, template.content]), + ...templateHtml.map((template): [string, string] => [`html-${template.key}`, template.content]), + ...templateMarkdown.map((template): [string, string] => [`markdown-${template.key}`, template.content]), + ...templateImage.map((template): [string, string] => [`image-${template.key}`, template.content]), + ...templatePdf.map((template): [string, string] => [`pdf-${template.key}`, template.content]), + ]); + + return { + default: defaultTemplateMap, + template: templateMap, + }; +} diff --git a/src/tools/__tests__/template.test.ts b/src/tools/__tests__/template.test.ts deleted file mode 100644 index a49899f..0000000 --- a/src/tools/__tests__/template.test.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { getTemplatePath } from '#/tools/files/getTemplatePath'; -import * as mnf from 'my-node-fp'; -import { describe, expect, test, vitest } from 'vitest'; - -vitest.mock('my-node-fp', async (importOriginal) => { - // eslint-disable-next-line @typescript-eslint/consistent-type-imports - const mod = await importOriginal(); - return { - ...mod, - }; -}); - -const basePathForGetTemplatePath = '/path01/path02/path03'; - -describe('getTemplatePath', () => { - test(`__dirname is 'src/tools/files/getTemplatePath'`, async () => { - const expectation = '/path01/templates'; - const getDirnameSpyOn = vitest.spyOn(mnf, 'getDirname').mockImplementation(async (dirname: string) => dirname); - const existsSpyOn = vitest.spyOn(mnf, 'exists').mockImplementation(async (existPath: string) => { - switch (existPath) { - case basePathForGetTemplatePath: - case expectation: - return true; - default: - return false; - } - }); - - const version = await getTemplatePath('/path01/path02/path03/path04'); - - getDirnameSpyOn.mockRestore(); - existsSpyOn.mockRestore(); - - expect(version).toEqual(expectation); - }); - - test(`__dirname is 'src/templates/html'`, async () => { - const expectation = '/path01/path02/templates'; - const getDirnameSpyOn = vitest.spyOn(mnf, 'getDirname').mockImplementation(async (dirname: string) => dirname); - const existsSpyOn = vitest.spyOn(mnf, 'exists').mockImplementation(async (existPath: string) => { - switch (existPath) { - case basePathForGetTemplatePath: - case expectation: - return true; - default: - return false; - } - }); - - console.log('INIT_CWD: ', process.env.INIT_CWD); - console.log('process.cwd(): ', process.cwd()); - - const version = await getTemplatePath('/path01/path02/path03/path04'); - - getDirnameSpyOn.mockRestore(); - existsSpyOn.mockRestore(); - - expect(version).toEqual(expectation); - }); - - test(`__dirname is 'templates'`, async () => { - const expectation = '/path01/path02/path03/path04/templates'; - const getDirnameSpyOn = vitest.spyOn(mnf, 'getDirname').mockImplementation(async (dirname: string) => dirname); - const existsSpyOn = vitest.spyOn(mnf, 'exists').mockImplementation(async (existPath: string) => { - switch (existPath) { - case basePathForGetTemplatePath: - case expectation: - return true; - default: - return false; - } - }); - - const version = await getTemplatePath('/path01/path02/path03/path04'); - - getDirnameSpyOn.mockRestore(); - existsSpyOn.mockRestore(); - - expect(version).toEqual(expectation); - }); - - test('not found, every where', async () => { - const getDirnameSpyOn = vitest.spyOn(mnf, 'getDirname').mockImplementation(async (dirname: string) => dirname); - const existsSpyOn = vitest.spyOn(mnf, 'exists').mockImplementation(async (existPath: string) => { - switch (existPath) { - case basePathForGetTemplatePath: - return true; - default: - return false; - } - }); - - await expect(async () => { - try { - const templatePath = await getTemplatePath('/path01/path02/path03/path04'); - console.log(templatePath); - } catch (err) { - getDirnameSpyOn.mockRestore(); - existsSpyOn.mockRestore(); - - throw err; - } - }).rejects.toThrowError(); - }); - - test('not exists, base directory', async () => { - await expect(async () => { - await getTemplatePath('/path01/path02/path03/path04'); - }).rejects.toThrowError(); - }); -}); diff --git a/src/tools/files/getTemplatePath.ts b/src/tools/files/getTemplatePath.ts deleted file mode 100644 index 4497309..0000000 --- a/src/tools/files/getTemplatePath.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { exists, getDirname } from 'my-node-fp'; -import path from 'path'; - -/** - * get template path - * - * istanbul ignore next - * */ -export async function getTemplatePath(filePath: string) { - const dirPath = await getDirname(filePath); - - if (dirPath == null && !(await exists(dirPath))) { - throw new Error(`invalid filePath: ${filePath}`); - } - - // find template directory for test-case - const firstDirPath = path.join(dirPath, '..', '..', '..', 'templates'); - - if (firstDirPath != null && (await exists(firstDirPath))) { - return firstDirPath; - } - - // find template directory for evaluate-template function - const secondDirPath = path.join(dirPath, '..', '..', 'templates'); - - if (secondDirPath != null && (await exists(secondDirPath))) { - return secondDirPath; - } - - // find template directory bundled js file - const thirdDirPath = path.join(dirPath, 'templates'); - - if (thirdDirPath != null && (await exists(thirdDirPath))) { - return thirdDirPath; - } - - throw new Error('Cannot found template and template path'); -} diff --git a/src/typeorm/columns/__tests__/column.test.ts b/src/typeorm/columns/__tests__/column.test.ts index 1f56f68..9f6467d 100644 --- a/src/typeorm/columns/__tests__/column.test.ts +++ b/src/typeorm/columns/__tests__/column.test.ts @@ -2,9 +2,9 @@ import * as env from '#/common/__tests__/test-config'; import type { IRecordMetadata } from '#/databases/interfaces/IRecordMetadata'; import { getColumnRecord } from '#/typeorm/columns/getColumnRecord'; import fastSafeStringify from 'fast-safe-stringify'; -import fs from 'fs'; import { parse } from 'jsonc-parser'; -import path from 'path'; +import fs from 'node:fs'; +import pathe from 'pathe'; import type { DataSource } from 'typeorm'; import { beforeAll, describe, expect, test } from 'vitest'; @@ -39,7 +39,7 @@ describe('getColumnRecord', () => { } const expectation = parse( - (await fs.promises.readFile(path.join(__dirname, 'expects', expectFileName))).toString(), + (await fs.promises.readFile(pathe.join(__dirname, 'expects', expectFileName))).toString(), ) as object; expect(columns).toMatchObject(expectation); diff --git a/src/typeorm/columns/__tests__/column.tools.test.ts b/src/typeorm/columns/__tests__/column.tools.test.ts index 2b7f2a2..fdb43f8 100644 --- a/src/typeorm/columns/__tests__/column.tools.test.ts +++ b/src/typeorm/columns/__tests__/column.tools.test.ts @@ -35,8 +35,8 @@ describe('getComment', () => { }); test('html comment', () => { - const comment = getComment({ format: CE_OUTPUT_FORMAT.HTML }, 'i-am-comment\ni-am-comment'); - expect(comment).toEqual('i-am-comment
i-am-comment'); + const comment = getComment({ format: CE_OUTPUT_FORMAT.HTML }, 'i-am-comment\n111\r\n222\n\r333'); + expect(comment).toEqual('i-am-comment
111
222
333'); }); test('pdf comment', () => { @@ -91,16 +91,26 @@ describe('getColumnType', () => { expect(columnType).toEqual('bigint'); }); - test('function-type without length + undefined', () => { + test('function-type without length + undefined + nullable', () => { const columnType = getColumnType({ type: Boolean, length: '200', isPrimary: false, isNullable: true }); expect(columnType).toEqual('boolean'); }); - test('function-type with length', () => { + test('function-type without length + undefined + non-nullable', () => { + const columnType = getColumnType({ type: Boolean, length: '200', isPrimary: false, isNullable: false }); + expect(columnType).toEqual('*boolean'); + }); + + test('function-type with length + nullable', () => { const columnType = getColumnType({ type: Boolean, length: '200', isPrimary: false, isNullable: true }, true); expect(columnType).toEqual('boolean(200)'); }); + test('function-type with length + non-nullable', () => { + const columnType = getColumnType({ type: Boolean, length: '200', isPrimary: false, isNullable: false }, true); + expect(columnType).toEqual('*boolean(200)'); + }); + test('function-type without length', () => { const columnType = getColumnType({ type: Boolean, length: '200', isPrimary: false, isNullable: true }, false); expect(columnType).toEqual('boolean'); diff --git a/src/typeorm/columns/getComment.ts b/src/typeorm/columns/getComment.ts index 8b1e713..80c93dd 100644 --- a/src/typeorm/columns/getComment.ts +++ b/src/typeorm/columns/getComment.ts @@ -10,7 +10,7 @@ export function getComment(option: Pick, comment: return comment.replace(/\r\n/g, '\\\\r\\\\n').replace(/\n\r/g, '\\\\n\\\\r').replace(/\n/g, '\\\\n'); } - if (option.format === 'html') { + if (option.format === CE_OUTPUT_FORMAT.HTML) { return comment.replace(/\r\n/g, '
').replace(/\n\r/g, '
').replace(/\n/g, '
'); } diff --git a/src/typeorm/entities/__tests__/entity.test.ts b/src/typeorm/entities/__tests__/entity.test.ts index 3d76803..87291de 100644 --- a/src/typeorm/entities/__tests__/entity.test.ts +++ b/src/typeorm/entities/__tests__/entity.test.ts @@ -6,9 +6,9 @@ import { getEntityPropertyName } from '#/typeorm/entities/getEntityPropertyName' import { getEntityRecord } from '#/typeorm/entities/getEntityRecord'; import { getEntityRecords } from '#/typeorm/entities/getEntityRecords'; import fastSafeStringify from 'fast-safe-stringify'; -import fs from 'fs'; import { parse } from 'jsonc-parser'; -import path from 'path'; +import fs from 'node:fs'; +import pathe from 'pathe'; import type { DataSource } from 'typeorm'; import { beforeAll, describe, expect, test } from 'vitest'; @@ -144,13 +144,13 @@ describe('getEntityRecord', () => { if (share.expect) { fs.writeFileSync( - path.join(__dirname, 'expects', `${expectFileName}`), + pathe.join(__dirname, 'expects', `${expectFileName}`), fastSafeStringify(tableDatas, undefined, 2), ); } const expectation = parse( - (await fs.promises.readFile(path.join(__dirname, 'expects', `${expectFileName}`))).toString(), + (await fs.promises.readFile(pathe.join(__dirname, 'expects', `${expectFileName}`))).toString(), ) as object; expect(tableDatas).toMatchObject(expectation); @@ -170,11 +170,11 @@ describe('getEntityRecords', () => { const records = getEntityRecords(share.dataSource, metadata); if (share.expect) { - fs.writeFileSync(path.join(__dirname, 'expects', `${expectFileName}`), fastSafeStringify(records, undefined, 2)); + fs.writeFileSync(pathe.join(__dirname, 'expects', `${expectFileName}`), fastSafeStringify(records, undefined, 2)); } const expectation = parse( - (await fs.promises.readFile(path.join(__dirname, 'expects', `${expectFileName}`))).toString(), + (await fs.promises.readFile(pathe.join(__dirname, 'expects', `${expectFileName}`))).toString(), ) as object; expect(records).toMatchObject(expectation); diff --git a/src/typeorm/getDataSource.ts b/src/typeorm/getDataSource.ts index d136e75..905fc96 100644 --- a/src/typeorm/getDataSource.ts +++ b/src/typeorm/getDataSource.ts @@ -2,11 +2,11 @@ import type { ICommonOption } from '#/configs/interfaces/ICommonOption'; import { loadDataSource } from '#/typeorm/loadDataSource'; import { isFalse } from 'my-easy-fp'; import { exists } from 'my-node-fp'; -import path from 'node:path'; +import pathe from 'pathe'; import { type DataSource } from 'typeorm'; export async function getDataSource(options: ICommonOption): Promise { - const dataSourcePath = path.resolve(options.dataSourcePath); + const dataSourcePath = pathe.resolve(options.dataSourcePath); if (isFalse(await exists(dataSourcePath))) { throw new Error(`Cannot found dataSource: ${dataSourcePath}`); diff --git a/src/typeorm/relations/__tests__/dedupe.relation.test.ts b/src/typeorm/relations/__tests__/dedupe.relation.test.ts index 2bd0946..b094ad7 100644 --- a/src/typeorm/relations/__tests__/dedupe.relation.test.ts +++ b/src/typeorm/relations/__tests__/dedupe.relation.test.ts @@ -2,9 +2,9 @@ import { CE_CHANGE_KIND } from '#/databases/const-enum/CE_CHANGE_KIND'; import type { IRelationRecord } from '#/databases/interfaces/IRelationRecord'; import { dedupeManaToManyRelationRecord } from '#/typeorm/relations/dedupeManaToManyRelationRecord'; import fastSafeStringify from 'fast-safe-stringify'; -import fs from 'fs'; import { parse } from 'jsonc-parser'; -import path from 'path'; +import fs from 'node:fs'; +import pathe from 'pathe'; import type { DataSource } from 'typeorm'; import { describe, expect, test } from 'vitest'; @@ -195,11 +195,11 @@ describe('dedupeManaToManyRelationRecord', () => { const deduped = dedupeManaToManyRelationRecord(relations); if (share.expect) { - fs.writeFileSync(path.join(__dirname, 'expects', `${expectFileName}`), fastSafeStringify(deduped, undefined, 2)); + fs.writeFileSync(pathe.join(__dirname, 'expects', `${expectFileName}`), fastSafeStringify(deduped, undefined, 2)); } const expectation = parse( - (await fs.promises.readFile(path.join(__dirname, 'expects', `${expectFileName}`))).toString(), + (await fs.promises.readFile(pathe.join(__dirname, 'expects', `${expectFileName}`))).toString(), ) as object; expect(deduped).toMatchObject(expectation); diff --git a/src/typeorm/relations/__tests__/relation.test.ts b/src/typeorm/relations/__tests__/relation.test.ts index 4b79728..2efd59e 100644 --- a/src/typeorm/relations/__tests__/relation.test.ts +++ b/src/typeorm/relations/__tests__/relation.test.ts @@ -3,10 +3,10 @@ import type { IRecordMetadata } from '#/databases/interfaces/IRecordMetadata'; import { getRelationRecord } from '#/typeorm/relations/getRelationRecord'; import { getRelationRecords } from '#/typeorm/relations/getRelationRecords'; import fastSafeStringify from 'fast-safe-stringify'; -import fs from 'fs'; import { parse } from 'jsonc-parser'; import { findOrThrow } from 'my-easy-fp'; -import path from 'path'; +import fs from 'node:fs'; +import pathe from 'pathe'; import type { DataSource } from 'typeorm'; import { afterAll, beforeAll, describe, expect, test, vitest } from 'vitest'; @@ -42,11 +42,14 @@ describe('getRelationRecord', () => { const relation = getRelationRecord(share.dataSource.entityMetadatas, relationMetadata, metadata); if (share.expect) { - fs.writeFileSync(path.join(__dirname, 'expects', `${expectFileName}`), fastSafeStringify(relation, undefined, 2)); + fs.writeFileSync( + pathe.join(__dirname, 'expects', `${expectFileName}`), + fastSafeStringify(relation, undefined, 2), + ); } const expectation = parse( - (await fs.promises.readFile(path.join(__dirname, 'expects', `${expectFileName}`))).toString(), + (await fs.promises.readFile(pathe.join(__dirname, 'expects', `${expectFileName}`))).toString(), ) as object; expect(relation).toMatchObject(expectation); @@ -72,11 +75,14 @@ describe('getRelationRecord', () => { const relation = getRelationRecord(share.dataSource.entityMetadatas, relationMetadata, metadata); if (share.expect) { - fs.writeFileSync(path.join(__dirname, 'expects', `${expectFileName}`), fastSafeStringify(relation, undefined, 2)); + fs.writeFileSync( + pathe.join(__dirname, 'expects', `${expectFileName}`), + fastSafeStringify(relation, undefined, 2), + ); } const expectation = parse( - (await fs.promises.readFile(path.join(__dirname, 'expects', `${expectFileName}`))).toString(), + (await fs.promises.readFile(pathe.join(__dirname, 'expects', `${expectFileName}`))).toString(), ) as object; expect(relation).toMatchObject(expectation); @@ -108,11 +114,14 @@ describe('getRelationRecord', () => { spyOnHandle.mockRestore(); if (share.expect) { - fs.writeFileSync(path.join(__dirname, 'expects', `${expectFileName}`), fastSafeStringify(relation, undefined, 2)); + fs.writeFileSync( + pathe.join(__dirname, 'expects', `${expectFileName}`), + fastSafeStringify(relation, undefined, 2), + ); } const expectation = parse( - (await fs.promises.readFile(path.join(__dirname, 'expects', `${expectFileName}`))).toString(), + (await fs.promises.readFile(pathe.join(__dirname, 'expects', `${expectFileName}`))).toString(), ) as object; expect(relation).toMatchObject(expectation); @@ -132,11 +141,14 @@ describe('getRelationRecords', () => { const relation = getRelationRecords(share.dataSource, metadata); if (share.expect) { - fs.writeFileSync(path.join(__dirname, 'expects', `${expectFileName}`), fastSafeStringify(relation, undefined, 2)); + fs.writeFileSync( + pathe.join(__dirname, 'expects', `${expectFileName}`), + fastSafeStringify(relation, undefined, 2), + ); } const expectation = parse( - (await fs.promises.readFile(path.join(__dirname, 'expects', `${expectFileName}`))).toString(), + (await fs.promises.readFile(pathe.join(__dirname, 'expects', `${expectFileName}`))).toString(), ) as object; expect(relation).toMatchObject(expectation); diff --git a/src/typeorm/relations/__tests__/relation.tools.test.ts b/src/typeorm/relations/__tests__/relation.tools.test.ts index 5c41053..1a76953 100644 --- a/src/typeorm/relations/__tests__/relation.tools.test.ts +++ b/src/typeorm/relations/__tests__/relation.tools.test.ts @@ -7,7 +7,7 @@ import fastSafeStringify from 'fast-safe-stringify'; import fs from 'fs'; import { parse } from 'jsonc-parser'; import { findOrThrow } from 'my-easy-fp'; -import path from 'path'; +import pathe from 'pathe'; import type { DataSource } from 'typeorm'; import { beforeAll, describe, expect, test } from 'vitest'; @@ -139,13 +139,13 @@ describe('getManyToManyJoinColumn', () => { if (share.expect) { fs.writeFileSync( - path.join(__dirname, 'expects', `${expectFileName}`), + pathe.join(__dirname, 'expects', `${expectFileName}`), fastSafeStringify(relations, undefined, 2), ); } const expectation = parse( - (await fs.promises.readFile(path.join(__dirname, 'expects', `${expectFileName}`))).toString(), + (await fs.promises.readFile(pathe.join(__dirname, 'expects', `${expectFileName}`))).toString(), ) as object; expect(relations).toMatchObject(expectation); @@ -169,7 +169,7 @@ describe('getManyToManyJoinColumn', () => { if (share.expect) { fs.writeFileSync( - path.join(__dirname, 'expects', `${expectFileName}`), + pathe.join(__dirname, 'expects', `${expectFileName}`), fastSafeStringify(relations, undefined, 2), ); } @@ -177,7 +177,7 @@ describe('getManyToManyJoinColumn', () => { relationMetadata.joinColumns = joinColumnsBackup; const expectation = parse( - (await fs.promises.readFile(path.join(__dirname, 'expects', `${expectFileName}`))).toString(), + (await fs.promises.readFile(pathe.join(__dirname, 'expects', `${expectFileName}`))).toString(), ) as object; expect(relations).toMatchObject(expectation); diff --git a/src/template/config/init-command.ts b/templates/config/init-command.eta similarity index 99% rename from src/template/config/init-command.ts rename to templates/config/init-command.eta index f288cec..c73c729 100644 --- a/src/template/config/init-command.ts +++ b/templates/config/init-command.eta @@ -1,4 +1,4 @@ -export const build = `{ +{ // directory for output files "output": "<%= it.config.output %>", @@ -99,4 +99,4 @@ export const build = `{ <% } else { %> // "image-format": "svg", <% } %> -}`; +} diff --git a/src/template/html/document-toc.ts b/templates/html/document-toc.eta similarity index 85% rename from src/template/html/document-toc.ts rename to templates/html/document-toc.eta index d5e054b..8c0600f 100644 --- a/src/template/html/document-toc.ts +++ b/templates/html/document-toc.eta @@ -1,4 +1,4 @@ -export const documentToC = ` diff --git a/src/template/html/document.ts b/templates/html/document.eta similarity index 85% rename from src/template/html/document.ts rename to templates/html/document.eta index a3735bc..079282b 100644 --- a/src/template/html/document.ts +++ b/templates/html/document.eta @@ -1,7 +1,4 @@ -import { CE_OUTPUT_COMPONENT } from '#/configs/const-enum/CE_OUTPUT_COMPONENT'; -import { CE_TEMPLATE_NAME } from '#/template/cosnt-enum/CE_TEMPLATE_NAME'; - -export const htmlDocument = ` + @@ -14,7 +11,7 @@ export const htmlDocument = ` - <%~ include('${CE_TEMPLATE_NAME.HTML_STYLE}', it); %> + <%~ include('html-style', it); %> @@ -26,14 +23,14 @@ export const htmlDocument = ` - <%~ include('${CE_TEMPLATE_NAME.HTML_DOCUMENT_TOC}', it); %> + <%~ include('html-document-toc', it); %>
@@ -68,7 +65,7 @@ export const htmlDocument = ` hide <% } %>">

<%= it.metadata.name %> <%= version.version %>

- <%~ include('${CE_TEMPLATE_NAME.HTML_TABLE}', { version, option: it.option, metadata: it.metadata }); %> + <%~ include('html-table', { version, option: it.option, metadata: it.metadata }); %>
<% }) %>
@@ -109,4 +106,4 @@ so.addEventListener('change', (e) => { .join(' '); }); -`; + diff --git a/src/template/mermaid/relation.ts b/templates/html/mermaid-diagram.eta similarity index 62% rename from src/template/mermaid/relation.ts rename to templates/html/mermaid-diagram.eta index eabcbaa..efb397b 100644 --- a/src/template/mermaid/relation.ts +++ b/templates/html/mermaid-diagram.eta @@ -1,4 +1,24 @@ -export const relation = `<% it.entity.relations.forEach((relation) => { %> +%%{init: {'theme':'<%= it.option.theme %>'}}%% + +erDiagram + +<% it.entities.forEach((entity) => { -%> +"<%= entity.dbName %>(<%= entity.name %>)" { +<% entity.columns.forEach((column) => { %> + <%= column.columnTypeWithLength %> <%= column.name -%> + <% if (column.attributeKey != null && column.attributeKey.length > 0) { %> + <%= column.attributeKey.join(',') -%> + <% } %> + <% if (column.comment != null && column.comment != '') { %> + "<%= column.comment -%>"<% -%> + <% } %> + +<% }) %> +} + +<% -%> + +<% entity.relations.forEach((relation) => { %> <% if (relation.isDuplicate) { return; } %> "<%= relation.dbName %>(<%= relation.name %>)"<% -%> <% if (relation.joinColumnOne && relation.joinColumnNullable) { %> @@ -23,4 +43,6 @@ o{ <% -%> "<%= relation.inverseEntityDBName %>(<%= relation.inverseEntityName %>)": <% -%> "<%= [relation.joinColumnName,relation.inverseJoinColumnName].filter((name) => name != null).sort().join(',') %>"<% -%> -<% }) %>`; +<% }) %> + +<% }) -%> diff --git a/src/template/html/mermaid-toc.ts b/templates/html/mermaid-toc.eta similarity index 80% rename from src/template/html/mermaid-toc.ts rename to templates/html/mermaid-toc.eta index 758900c..9908227 100644 --- a/src/template/html/mermaid-toc.ts +++ b/templates/html/mermaid-toc.eta @@ -1,4 +1,4 @@ -export const documentToC = `