From f52ba8f4c45c871e54edf8a61be6dba92fe204f7 Mon Sep 17 00:00:00 2001 From: Kononnable Date: Sun, 22 Dec 2019 16:58:13 +0100 Subject: [PATCH 1/4] Ignore tables parameter (#120) --- src/IConnectionOptions.ts | 4 ++- src/drivers/AbstractDriver.ts | 15 +++++--- src/drivers/MssqlDriver.ts | 12 +++++-- src/drivers/MysqlDriver.ts | 12 +++++-- src/drivers/OracleDriver.ts | 12 +++++-- src/drivers/PostgresDriver.ts | 12 +++++-- src/drivers/SqliteDriver.ts | 12 +++++-- src/index.ts | 36 ++++++++++++++++++- test/drivers/MssqlDriver.test.ts | 2 +- test/integration/runTestsFromPath.test.ts | 6 ++-- .../modelCustomization.test.ts | 2 ++ test/utils/GeneralTestUtils.ts | 18 ++++++---- 12 files changed, 118 insertions(+), 25 deletions(-) diff --git a/src/IConnectionOptions.ts b/src/IConnectionOptions.ts index 8be2079..5aef82d 100644 --- a/src/IConnectionOptions.ts +++ b/src/IConnectionOptions.ts @@ -16,6 +16,7 @@ export default interface IConnectionOptions { | "sqlite"; schemaName: string; ssl: boolean; + skipTables: string[]; } export function getDefaultConnectionOptions(): IConnectionOptions { @@ -27,7 +28,8 @@ export function getDefaultConnectionOptions(): IConnectionOptions { password: "", databaseType: undefined as any, schemaName: "", - ssl: false + ssl: false, + skipTables: [] }; return connectionOptions; } diff --git a/src/drivers/AbstractDriver.ts b/src/drivers/AbstractDriver.ts index bb6ee21..a086f00 100644 --- a/src/drivers/AbstractDriver.ts +++ b/src/drivers/AbstractDriver.ts @@ -69,7 +69,8 @@ export default abstract class AbstractDriver { public abstract GetAllTablesQuery: ( schema: string, - dbNames: string + dbNames: string, + tableNames: string[] ) => Promise< { TABLE_SCHEMA: string; @@ -186,7 +187,8 @@ export default abstract class AbstractDriver { ); dbModel = await this.GetAllTables( sqlEscapedSchema, - connectionOptions.databaseName + connectionOptions.databaseName, + connectionOptions.skipTables ); await this.GetCoulmnsFromEntity( dbModel, @@ -214,9 +216,14 @@ export default abstract class AbstractDriver { public async GetAllTables( schema: string, - dbNames: string + dbNames: string, + tableNames: string[] ): Promise { - const response = await this.GetAllTablesQuery(schema, dbNames); + const response = await this.GetAllTablesQuery( + schema, + dbNames, + tableNames + ); const ret: Entity[] = [] as Entity[]; response.forEach(val => { ret.push({ diff --git a/src/drivers/MssqlDriver.ts b/src/drivers/MssqlDriver.ts index a541c85..e760810 100644 --- a/src/drivers/MssqlDriver.ts +++ b/src/drivers/MssqlDriver.ts @@ -24,8 +24,16 @@ export default class MssqlDriver extends AbstractDriver { private Connection: MSSQL.ConnectionPool; - public GetAllTablesQuery = async (schema: string, dbNames: string) => { + public GetAllTablesQuery = async ( + schema: string, + dbNames: string, + tableNames: string[] + ) => { const request = new MSSQL.Request(this.Connection); + const tableCondition = + tableNames.length > 0 + ? ` AND NOT TABLE_NAME IN ('${tableNames.join("','")}')` + : ""; const response: { TABLE_SCHEMA: string; TABLE_NAME: string; @@ -35,7 +43,7 @@ export default class MssqlDriver extends AbstractDriver { `SELECT TABLE_SCHEMA,TABLE_NAME, table_catalog as "DB_NAME" FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE' and TABLE_SCHEMA in (${schema}) AND TABLE_CATALOG in (${MssqlDriver.escapeCommaSeparatedList( dbNames - )})` + )}) ${tableCondition}` ) ).recordset; return response; diff --git a/src/drivers/MysqlDriver.ts b/src/drivers/MysqlDriver.ts index b7185fc..f63212c 100644 --- a/src/drivers/MysqlDriver.ts +++ b/src/drivers/MysqlDriver.ts @@ -26,7 +26,15 @@ export default class MysqlDriver extends AbstractDriver { private Connection: MYSQL.Connection; - public GetAllTablesQuery = async (schema: string, dbNames: string) => { + public GetAllTablesQuery = async ( + schema: string, + dbNames: string, + tableNames: string[] + ) => { + const tableCondition = + tableNames.length > 0 + ? ` AND NOT TABLE_NAME IN ('${tableNames.join("','")}')` + : ""; const response = this.ExecQuery<{ TABLE_SCHEMA: string; TABLE_NAME: string; @@ -36,7 +44,7 @@ export default class MysqlDriver extends AbstractDriver { WHERE table_type='BASE TABLE' AND table_schema IN (${MysqlDriver.escapeCommaSeparatedList( dbNames - )})`); + )}) ${tableCondition}`); return response; }; diff --git a/src/drivers/OracleDriver.ts b/src/drivers/OracleDriver.ts index bf045a7..f5f7c04 100644 --- a/src/drivers/OracleDriver.ts +++ b/src/drivers/OracleDriver.ts @@ -36,14 +36,22 @@ export default class OracleDriver extends AbstractDriver { } } - public GetAllTablesQuery = async () => { + public GetAllTablesQuery = async ( + schema: string, + dbNames: string, + tableNames: string[] + ) => { + const tableCondition = + tableNames.length > 0 + ? ` AND NOT TABLE_NAME IN ('${tableNames.join("','")}')` + : ""; const response: { TABLE_SCHEMA: string; TABLE_NAME: string; DB_NAME: string; }[] = ( await this.Connection.execute( - `SELECT NULL AS TABLE_SCHEMA, TABLE_NAME, NULL AS DB_NAME FROM all_tables WHERE owner = (select user from dual)` + `SELECT NULL AS TABLE_SCHEMA, TABLE_NAME, NULL AS DB_NAME FROM all_tables WHERE owner = (select user from dual) ${tableCondition}` ) ).rows!; return response; diff --git a/src/drivers/PostgresDriver.ts b/src/drivers/PostgresDriver.ts index 4f6f105..2703f59 100644 --- a/src/drivers/PostgresDriver.ts +++ b/src/drivers/PostgresDriver.ts @@ -24,14 +24,22 @@ export default class PostgresDriver extends AbstractDriver { private Connection: PG.Client; - public GetAllTablesQuery = async (schema: string) => { + public GetAllTablesQuery = async ( + schema: string, + dbNames: string, + tableNames: string[] + ) => { + const tableCondition = + tableNames.length > 0 + ? ` AND NOT table_name IN ('${tableNames.join("','")}')` + : ""; const response: { TABLE_SCHEMA: string; TABLE_NAME: string; DB_NAME: string; }[] = ( await this.Connection.query( - `SELECT table_schema as "TABLE_SCHEMA",table_name as "TABLE_NAME", table_catalog as "DB_NAME" FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE' AND table_schema in (${schema}) ` + `SELECT table_schema as "TABLE_SCHEMA",table_name as "TABLE_NAME", table_catalog as "DB_NAME" FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE' AND table_schema in (${schema}) ${tableCondition}` ) ).rows; return response; diff --git a/src/drivers/SqliteDriver.ts b/src/drivers/SqliteDriver.ts index 64ce684..ac99030 100644 --- a/src/drivers/SqliteDriver.ts +++ b/src/drivers/SqliteDriver.ts @@ -30,10 +30,18 @@ export default class SqliteDriver extends AbstractDriver { public GetAllTablesQuery: any; - public async GetAllTables(): Promise { + public async GetAllTables( + schema: string, + dbNames: string, + tableNames: string[] + ): Promise { const ret: Entity[] = [] as Entity[]; + const tableCondition = + tableNames.length > 0 + ? ` AND NOT tbl_name IN ('${tableNames.join("','")}')` + : ""; const rows = await this.ExecQuery<{ tbl_name: string; sql: string }>( - `SELECT tbl_name, sql FROM "sqlite_master" WHERE "type" = 'table' AND name NOT LIKE 'sqlite_%'` + `SELECT tbl_name, sql FROM "sqlite_master" WHERE "type" = 'table' AND name NOT LIKE 'sqlite_%' ${tableCondition}` ); rows.forEach(val => { if (val.sql.includes("AUTOINCREMENT")) { diff --git a/src/index.ts b/src/index.ts index e84e68e..b0264fb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -243,7 +243,13 @@ function checkYargsParameters(options: options): options { boolean: true, default: !options.generationOptions.pluralizeNames, describe: - "Disable pluralization of OneToMany, ManyToMany relation names." + "Disable pluralization of OneToMany, ManyToMany relation names" + }, + skipTables: { + string: true, + default: options.connectionOptions.skipTables.join(","), + describe: + "Skip schema generation for specific tables. You can pass multiple values separated by comma" }, strictMode: { choices: ["none", "?", "!"], @@ -280,6 +286,7 @@ function checkYargsParameters(options: options): options { options.generationOptions.resultsPath = argv.o; options.generationOptions.pluralizeNames = !argv.disablePluralization; options.generationOptions.strictMode = argv.strictMode as IGenerationOptions["strictMode"]; + options.connectionOptions.skipTables = argv.skipTables.split(","); return options; } @@ -387,6 +394,33 @@ async function useInquirer(options: options): Promise { ]) ).dbName; } + + const ignoreSpecyficTables = ( + await inquirer.prompt([ + { + default: + options.connectionOptions.skipTables.length === 0 + ? "All of them" + : "Ignore specific tables", + message: "Generate schema for tables:", + choices: ["All of them", "Ignore specific tables"], + name: "specyficTables", + type: "list" + } + ]) + ).specyficTables; + if (ignoreSpecyficTables === "Ignore specific tables") { + const { tableNames } = await inquirer.prompt({ + default: options.connectionOptions.skipTables.join(","), + message: "Table names(separated by comma)", + name: "tableNames", + type: "input" + }); + options.connectionOptions.skipTables = tableNames.split(","); + } else { + options.connectionOptions.skipTables = []; + } + options.generationOptions.resultsPath = ( await inquirer.prompt([ { diff --git a/test/drivers/MssqlDriver.test.ts b/test/drivers/MssqlDriver.test.ts index bfa577f..cff8cdc 100644 --- a/test/drivers/MssqlDriver.test.ts +++ b/test/drivers/MssqlDriver.test.ts @@ -47,7 +47,7 @@ describe("MssqlDriver", () => { return response; } }); - const result = await driver.GetAllTables("schema", "db"); + const result = await driver.GetAllTables("schema", "db", []); const expectedResult = [] as Entity[]; const y: Entity = { columns: [], diff --git a/test/integration/runTestsFromPath.test.ts b/test/integration/runTestsFromPath.test.ts index c394a6f..ec5eb24 100644 --- a/test/integration/runTestsFromPath.test.ts +++ b/test/integration/runTestsFromPath.test.ts @@ -320,7 +320,8 @@ async function prepareTestRuns( password: String(process.env.MYSQL_Password), databaseType: "mysql", schemaName: "ignored", - ssl: yn(process.env.MYSQL_SSL, { default: false }) + ssl: yn(process.env.MYSQL_SSL, { default: false }), + skipTables: [] }; break; case "mariadb": @@ -332,7 +333,8 @@ async function prepareTestRuns( password: String(process.env.MARIADB_Password), databaseType: "mariadb", schemaName: "ignored", - ssl: yn(process.env.MARIADB_SSL, { default: false }) + ssl: yn(process.env.MARIADB_SSL, { default: false }), + skipTables: [] }; break; diff --git a/test/modelCustomization/modelCustomization.test.ts b/test/modelCustomization/modelCustomization.test.ts index da60c12..b474300 100644 --- a/test/modelCustomization/modelCustomization.test.ts +++ b/test/modelCustomization/modelCustomization.test.ts @@ -15,6 +15,8 @@ import { compileGeneratedModel } from "../integration/runTestsFromPath.test"; chai.use(chaiSubset); const { expect } = chai; + +// TODO: test for connectionOptions.specyficTables describe("Model customization phase", async () => { const generateSampleData: () => Entity[] = () => [ { diff --git a/test/utils/GeneralTestUtils.ts b/test/utils/GeneralTestUtils.ts index bf2fe82..6821ee6 100644 --- a/test/utils/GeneralTestUtils.ts +++ b/test/utils/GeneralTestUtils.ts @@ -29,7 +29,8 @@ export async function createMSSQLModels( password: String(process.env.MSSQL_Password), databaseType: "mssql", schemaName: "dbo,sch1,sch2", - ssl: yn(process.env.MSSQL_SSL, { default: false }) + ssl: yn(process.env.MSSQL_SSL, { default: false }), + skipTables: [] }; await driver.ConnectToServer(connectionOptions); connectionOptions.databaseName = String(process.env.MSSQL_Database); @@ -80,7 +81,8 @@ export async function createPostgresModels( password: String(process.env.POSTGRES_Password), databaseType: "postgres", schemaName: "public,sch1,sch2", - ssl: yn(process.env.POSTGRES_SSL, { default: false }) + ssl: yn(process.env.POSTGRES_SSL, { default: false }), + skipTables: [] }; await driver.ConnectToServer(connectionOptions); connectionOptions.databaseName = String(process.env.POSTGRES_Database); @@ -130,7 +132,8 @@ export async function createSQLiteModels( password: "", databaseType: "sqlite", schemaName: "", - ssl: false + ssl: false, + skipTables: [] }; const connOpt: ConnectionOptions = { @@ -164,7 +167,8 @@ export async function createMysqlModels( password: String(process.env.MYSQL_Password), databaseType: "mysql", schemaName: "ignored", - ssl: yn(process.env.MYSQL_SSL, { default: false }) + ssl: yn(process.env.MYSQL_SSL, { default: false }), + skipTables: [] }; await driver.ConnectToServer(connectionOptions); @@ -206,7 +210,8 @@ export async function createMariaDBModels( password: String(process.env.MARIADB_Password), databaseType: "mariadb", schemaName: "ignored", - ssl: yn(process.env.MARIADB_SSL, { default: false }) + ssl: yn(process.env.MARIADB_SSL, { default: false }), + skipTables: [] }; await driver.ConnectToServer(connectionOptions); @@ -250,7 +255,8 @@ export async function createOracleDBModels( password: String(process.env.ORACLE_PasswordSys), databaseType: "oracle", schemaName: String(process.env.ORACLE_Username), - ssl: yn(process.env.ORACLE_SSL, { default: false }) + ssl: yn(process.env.ORACLE_SSL, { default: false }), + skipTables: [] }; await driver.ConnectToServer(connectionOptions); connectionOptions.user = String(process.env.ORACLE_Username); From 57c6c307b06f8fcf31baff306a959a69ad126580 Mon Sep 17 00:00:00 2001 From: Kononnable Date: Tue, 24 Dec 2019 09:53:53 +0100 Subject: [PATCH 2/4] Run eslint on models generated by tests --- .eslintrc.js | 2 +- package-lock.json | 16 +++++++ package.json | 1 + src/templates/entity.mst | 2 +- test/configs/.eslintrc.js | 30 +++++++++++++ test/configs/tsconfig.json | 17 +++++++ test/integration/runTestsFromPath.test.ts | 10 +++++ tsconfig.json | 55 ++++++++++++----------- 8 files changed, 104 insertions(+), 29 deletions(-) create mode 100644 test/configs/.eslintrc.js create mode 100644 test/configs/tsconfig.json diff --git a/.eslintrc.js b/.eslintrc.js index 7046abe..fc4df69 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -36,7 +36,7 @@ module.exports = { settings: { "import/resolver": { node: { - extensions: [".js", ".jsx", ".ts", ".tsx"] + extensions: [".ts"] } } } diff --git a/package-lock.json b/package-lock.json index 1c9829f..08564cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -274,12 +274,28 @@ "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, + "@types/eslint": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-6.1.3.tgz", + "integrity": "sha512-llYf1QNZaDweXtA7uY6JczcwHmFwJL9TpK3E6sY0B18l6ulDT6VWNMAdEjYccFHiDfxLPxffd8QmSDV4QUUspA==", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, "@types/eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", "dev": true }, + "@types/estree": { + "version": "0.0.41", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.41.tgz", + "integrity": "sha512-rIAmXyJlqw4KEBO7+u9gxZZSQHaCNnIzYrnNmYVpgfJhxTqO0brCX0SYpqUTkVI5mwwUwzmtspLBGBKroMeynA==", + "dev": true + }, "@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", diff --git a/package.json b/package.json index d70caee..4ec7766 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@types/chai": "^4.2.7", "@types/chai-as-promised": "^7.1.2", "@types/chai-subset": "^1.3.3", + "@types/eslint": "^6.1.3", "@types/fs-extra": "^8.0.1", "@types/handlebars": "^4.1.0", "@types/inquirer": "^6.5.0", diff --git a/src/templates/entity.mst b/src/templates/entity.mst index 6e16182..ce84e6b 100644 --- a/src/templates/entity.mst +++ b/src/templates/entity.mst @@ -13,7 +13,7 @@ import { {{toEntityName .}} } from './{{toFileName .}}' { name: "{{name}}", referencedColumnName: "{{toPropertyName referencedColumnName}}" }, {{/inline}} {{#*inline "Relation"}} -@{{relationType}}(()=>{{toEntityName relatedTable}},{{toEntityName relatedTable}}=>{{toEntityName relatedTable}}.{{toPropertyName relatedField}}{{#if relationOptions}},{ {{json relationOptions}} }{{/if}}) +@{{relationType}}(()=>{{toEntityName relatedTable}},{{toEntityName relatedTable}}_=>{{toEntityName relatedTable}}_.{{toPropertyName relatedField}}{{#if relationOptions}},{ {{json relationOptions}} }{{/if}}) {{#if joinColumnOptions}}@JoinColumn([{{#joinColumnOptions}}{{> JoinColumnOptions}}{{/joinColumnOptions}}]){{/if}} {{#joinTableOptions}}@JoinTable({ name:"{{name}}", joinColumns:[{{#joinColumns}}{{> JoinColumnOptions}}{{/joinColumns}}],inverseJoinColumns:[{{#inverseJoinColumns}}{{> JoinColumnOptions}}{{/inverseJoinColumns}}],{{#database}}database:"{{.}}",{{/database}}{{#schema}}schema:"{{.}}"{{/schema}} }){{/joinTableOptions}} {{printPropertyVisibility}}{{toPropertyName fieldName}}{{strictMode}}:{{toRelation (toEntityName relatedTable) relationType}}; diff --git a/test/configs/.eslintrc.js b/test/configs/.eslintrc.js new file mode 100644 index 0000000..c2a28ca --- /dev/null +++ b/test/configs/.eslintrc.js @@ -0,0 +1,30 @@ +module.exports = { + env: { + node: true, + }, + extends: [ + "airbnb-base", + "plugin:@typescript-eslint/recommended", + "prettier", + "prettier/@typescript-eslint" + ], + parser: "@typescript-eslint/parser", + parserOptions: { + project: "test/configs/tsconfig.json" + }, + plugins: ["@typescript-eslint"], + rules: { // TODO: remove some rule overrides after disabling eslint on model customization tests(?) + "import/extensions": ["off"], + "import/prefer-default-export": ["off"], + "@typescript-eslint/no-explicit-any": ["off"], + "@typescript-eslint/camelcase": ["off"], + "@typescript-eslint/class-name-casing": ["off"] + }, + settings: { + "import/resolver": { + node: { + extensions: [".ts"] + } + } + } +}; diff --git a/test/configs/tsconfig.json b/test/configs/tsconfig.json new file mode 100644 index 0000000..8d56403 --- /dev/null +++ b/test/configs/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2017", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "moduleResolution": "node", + "newLine": "LF", + "typeRoots": [ + "../../node_modules/@types" + ], + "resolveJsonModule": true, + }, + "include": [ + "../../output" + ] +} diff --git a/test/integration/runTestsFromPath.test.ts b/test/integration/runTestsFromPath.test.ts index ec5eb24..10981de 100644 --- a/test/integration/runTestsFromPath.test.ts +++ b/test/integration/runTestsFromPath.test.ts @@ -6,6 +6,7 @@ import * as path from "path"; import * as chaiSubset from "chai-subset"; import * as flatMap from "array.prototype.flatmap"; import yn from "yn"; +import { CLIEngine } from "eslint"; import EntityFileToJson from "../utils/EntityFileToJson"; import { createDriver, dataCollectionPhase } from "../../src/Engine"; import * as GTU from "../utils/GeneralTestUtils"; @@ -277,6 +278,15 @@ export function compileGeneratedModel(filesGenPath: string, drivers: string[]) { compiledWithoutErrors, "Errors detected while compiling generated model" ).to.equal(true); + + + const cli = new CLIEngine({ configFile: "test/configs/.eslintrc.js" }); + const lintReport = cli.executeOnFiles(currentDirectoryFiles) + lintReport.results.forEach(result => result.messages.forEach(message => { + console.error(`${result.filePath}:${message.line} - ${message.message}`) + })) + expect(lintReport.errorCount).to.equal(0) + expect(lintReport.warningCount).to.equal(0) } async function prepareTestRuns( diff --git a/tsconfig.json b/tsconfig.json index 93cff05..3c6e9eb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,29 +1,30 @@ { - "compilerOptions": { - "module": "commonjs", - "target": "es2017", - "noImplicitAny": false, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "sourceMap": true, - "declaration": false, - "noFallthroughCasesInSwitch": true, - "noImplicitReturns": true, - "stripInternal": true, - "strictNullChecks": true, - "moduleResolution": "node", - "newLine": "LF", - "outDir": "dist", - "lib": [ - "es2019","es2019.array" - ], - "typeRoots": [ - "./node_modules/@types" - ], - "resolveJsonModule": true, - }, - "include": [ - "src", - "test" - ] + "compilerOptions": { + "module": "commonjs", + "target": "es2017", + "noImplicitAny": false, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "sourceMap": true, + "declaration": false, + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "stripInternal": true, + "strictNullChecks": true, + "moduleResolution": "node", + "newLine": "LF", + "outDir": "dist", + "lib": [ + "es2019", + "es2019.array" + ], + "typeRoots": [ + "./node_modules/@types" + ], + "resolveJsonModule": true, + }, + "include": [ + "src", + "test", + ] } From dc4208ad0f42ad8516cb7c1d7c48dfc269b82934 Mon Sep 17 00:00:00 2001 From: Kononnable Date: Tue, 24 Dec 2019 10:40:36 +0100 Subject: [PATCH 3/4] Option to generate index file (#174) --- src/IGenerationOptions.ts | 4 +- src/ModelGeneration.ts | 59 +++++++++++++++++-- src/index.ts | 16 ++++- src/templates/index.mst | 3 + .../modelCustomization.test.ts | 48 +++++++++++++++ 5 files changed, 123 insertions(+), 7 deletions(-) create mode 100644 src/templates/index.mst diff --git a/src/IGenerationOptions.ts b/src/IGenerationOptions.ts index a12b3c4..1cb8568 100644 --- a/src/IGenerationOptions.ts +++ b/src/IGenerationOptions.ts @@ -18,6 +18,7 @@ export default interface IGenerationOptions { relationIds: boolean; strictMode: "none" | "?" | "!"; skipSchema: boolean; + indexFile: boolean; } export function getDefaultGenerationOptions(): IGenerationOptions { const generationOptions: IGenerationOptions = { @@ -34,7 +35,8 @@ export function getDefaultGenerationOptions(): IGenerationOptions { customNamingStrategyPath: "", relationIds: false, strictMode: "none", - skipSchema: false + skipSchema: false, + indexFile: false }; return generationOptions; } diff --git a/src/ModelGeneration.ts b/src/ModelGeneration.ts index dfc8e1f..758dc2a 100644 --- a/src/ModelGeneration.ts +++ b/src/ModelGeneration.ts @@ -14,8 +14,7 @@ export default function modelGenerationPhase( databaseModel: Entity[] ): void { createHandlebarsHelpers(generationOptions); - const templatePath = path.resolve(__dirname, "templates", "entity.mst"); - const template = fs.readFileSync(templatePath, "UTF-8"); + const resultPath = generationOptions.resultsPath; if (!fs.existsSync(resultPath)) { fs.mkdirSync(resultPath); @@ -29,7 +28,24 @@ export default function modelGenerationPhase( fs.mkdirSync(entitiesPath); } } - const compliedTemplate = Handlebars.compile(template, { + if (generationOptions.indexFile) { + createIndexFile(databaseModel, generationOptions, entitiesPath); + } + generateModels(databaseModel, generationOptions, entitiesPath); +} + +function generateModels( + databaseModel: Entity[], + generationOptions: IGenerationOptions, + entitiesPath: string +) { + const entityTemplatePath = path.resolve( + __dirname, + "templates", + "entity.mst" + ); + const entityTemplate = fs.readFileSync(entityTemplatePath, "UTF-8"); + const entityCompliedTemplate = Handlebars.compile(entityTemplate, { noEscape: true }); databaseModel.forEach(element => { @@ -54,7 +70,7 @@ export default function modelGenerationPhase( entitiesPath, `${casedFileName}.ts` ); - const rendered = compliedTemplate(element); + const rendered = entityCompliedTemplate(element); const withImportStatements = removeUnusedImports(rendered); const formatted = Prettier.format(withImportStatements, { parser: "typescript" @@ -65,6 +81,41 @@ export default function modelGenerationPhase( }); }); } + +function createIndexFile( + databaseModel: Entity[], + generationOptions: IGenerationOptions, + entitiesPath: string +) { + const templatePath = path.resolve(__dirname, "templates", "index.mst"); + const template = fs.readFileSync(templatePath, "UTF-8"); + const compliedTemplate = Handlebars.compile(template, { + noEscape: true + }); + const rendered = compliedTemplate({ entities: databaseModel }); + const formatted = Prettier.format(rendered, { + parser: "typescript" + }); + let fileName = "index"; + switch (generationOptions.convertCaseFile) { + case "camel": + fileName = changeCase.camelCase(fileName); + break; + case "param": + fileName = changeCase.paramCase(fileName); + break; + case "pascal": + fileName = changeCase.pascalCase(fileName); + break; + default: + } + const resultFilePath = path.resolve(entitiesPath, `${fileName}.ts`); + fs.writeFileSync(resultFilePath, formatted, { + encoding: "UTF-8", + flag: "w" + }); +} + function removeUnusedImports(rendered: string) { const openBracketIndex = rendered.indexOf("{") + 1; const closeBracketIndex = rendered.indexOf("}"); diff --git a/src/index.ts b/src/index.ts index b0264fb..07375ba 100644 --- a/src/index.ts +++ b/src/index.ts @@ -255,6 +255,11 @@ function checkYargsParameters(options: options): options { choices: ["none", "?", "!"], default: options.generationOptions.strictMode, describe: "Mark fields as optional(?) or non-null(!)" + }, + index: { + boolean: true, + default: options.generationOptions.indexFile, + describe: "Generate index file" } }); @@ -272,6 +277,7 @@ function checkYargsParameters(options: options): options { : standardSchema; options.connectionOptions.ssl = argv.ssl; options.connectionOptions.user = argv.u || standardUser; + options.connectionOptions.skipTables = argv.skipTables.split(","); options.generationOptions.activeRecord = argv.a; options.generationOptions.generateConstructor = argv.generateConstructor; options.generationOptions.convertCaseEntity = argv.ce as IGenerationOptions["convertCaseEntity"]; @@ -286,7 +292,7 @@ function checkYargsParameters(options: options): options { options.generationOptions.resultsPath = argv.o; options.generationOptions.pluralizeNames = !argv.disablePluralization; options.generationOptions.strictMode = argv.strictMode as IGenerationOptions["strictMode"]; - options.connectionOptions.skipTables = argv.skipTables.split(","); + options.generationOptions.indexFile = argv.index; return options; } @@ -499,9 +505,14 @@ async function useInquirer(options: options): Promise { }, { name: - "Pluralize OneToMany, ManyToMany relation names.", + "Pluralize OneToMany, ManyToMany relation names", value: "pluralize", checked: options.generationOptions.pluralizeNames + }, + { + name: "Generate index file", + value: "index", + checked: options.generationOptions.indexFile } ], message: "Available customizations", @@ -555,6 +566,7 @@ async function useInquirer(options: options): Promise { options.generationOptions.generateConstructor = customizations.includes( "constructor" ); + options.generationOptions.indexFile = customizations.includes("index"); if (customizations.includes("namingStrategy")) { const namingStrategyPath = ( diff --git a/src/templates/index.mst b/src/templates/index.mst new file mode 100644 index 0000000..928dd19 --- /dev/null +++ b/src/templates/index.mst @@ -0,0 +1,3 @@ +{{#entities~}} +export { {{toEntityName tscName}} } from './{{toFileName tscName}}' +{{/entities~}} \ No newline at end of file diff --git a/test/modelCustomization/modelCustomization.test.ts b/test/modelCustomization/modelCustomization.test.ts index b474300..acbde5f 100644 --- a/test/modelCustomization/modelCustomization.test.ts +++ b/test/modelCustomization/modelCustomization.test.ts @@ -665,4 +665,52 @@ describe("Model customization phase", async () => { compileGeneratedModel(generationOptions.resultsPath, [""]); }) }) + describe("index file generation", () => { + it("enabled", async () => { + + const data = generateSampleData(); + const generationOptions = generateGenerationOptions(); + generationOptions.indexFile = true; + clearGenerationDir(); + + const customizedModel = modelCustomizationPhase( + data, + generationOptions, + {} + ); + modelGenerationPhase( + getDefaultConnectionOptions(), + generationOptions, + customizedModel + ); + const filesGenPath = path.resolve(resultsPath, "entities"); + const indexFileContent = fs + .readFileSync(path.resolve(filesGenPath, "Index.ts")) + .toString(); + expect(indexFileContent).to.contain('export { Post } from "./Post";'); + expect(indexFileContent).to.contain('export { PostAuthor } from "./PostAuthor";'); + compileGeneratedModel(generationOptions.resultsPath, [""]); + }) + it("disabled", async () => { + + const data = generateSampleData(); + const generationOptions = generateGenerationOptions(); + generationOptions.pluralizeNames = false; + clearGenerationDir(); + + const customizedModel = modelCustomizationPhase( + data, + generationOptions, + {} + ); + modelGenerationPhase( + getDefaultConnectionOptions(), + generationOptions, + customizedModel + ); + const filesGenPath = path.resolve(resultsPath, "entities"); + expect(fs.existsSync(path.resolve(filesGenPath, "Index.ts"))).to.equal(false); + compileGeneratedModel(generationOptions.resultsPath, [""]); + }) + }) }); From 88d3947d32d9f24c6fd1e8bd2e6288df05b64bfb Mon Sep 17 00:00:00 2001 From: Kononnable Date: Wed, 25 Dec 2019 20:35:38 +0100 Subject: [PATCH 4/4] ability to generate models with default exports (instead of name exports) --- src/IGenerationOptions.ts | 4 ++- src/ModelGeneration.ts | 8 +++++ src/index.ts | 20 +++++++++++ src/templates/entity.mst | 4 +-- src/templates/index.mst | 6 ++-- .../modelCustomization.test.ts | 35 +++++++++++++++++-- 6 files changed, 69 insertions(+), 8 deletions(-) diff --git a/src/IGenerationOptions.ts b/src/IGenerationOptions.ts index 1cb8568..7e3ae8f 100644 --- a/src/IGenerationOptions.ts +++ b/src/IGenerationOptions.ts @@ -19,6 +19,7 @@ export default interface IGenerationOptions { strictMode: "none" | "?" | "!"; skipSchema: boolean; indexFile: boolean; + exportType: "named" | "default"; } export function getDefaultGenerationOptions(): IGenerationOptions { const generationOptions: IGenerationOptions = { @@ -36,7 +37,8 @@ export function getDefaultGenerationOptions(): IGenerationOptions { relationIds: false, strictMode: "none", skipSchema: false, - indexFile: false + indexFile: false, + exportType: "named" }; return generationOptions; } diff --git a/src/ModelGeneration.ts b/src/ModelGeneration.ts index 758dc2a..c723447 100644 --- a/src/ModelGeneration.ts +++ b/src/ModelGeneration.ts @@ -211,6 +211,14 @@ function createHandlebarsHelpers(generationOptions: IGenerationOptions): void { return retVal; } ); + Handlebars.registerHelper("defaultExport", () => + generationOptions.exportType === "default" ? "default" : "" + ); + Handlebars.registerHelper("localImport", (entityName: string) => + generationOptions.exportType === "default" + ? entityName + : `{${entityName}}` + ); Handlebars.registerHelper("strictMode", () => generationOptions.strictMode !== "none" ? generationOptions.strictMode diff --git a/src/index.ts b/src/index.ts index 07375ba..954f298 100644 --- a/src/index.ts +++ b/src/index.ts @@ -260,6 +260,11 @@ function checkYargsParameters(options: options): options { boolean: true, default: options.generationOptions.indexFile, describe: "Generate index file" + }, + defaultExport: { + boolean: true, + default: options.generationOptions.exportType === "default", + describe: "Generate index file" } }); @@ -293,6 +298,9 @@ function checkYargsParameters(options: options): options { options.generationOptions.pluralizeNames = !argv.disablePluralization; options.generationOptions.strictMode = argv.strictMode as IGenerationOptions["strictMode"]; options.generationOptions.indexFile = argv.index; + options.generationOptions.exportType = argv.defaultExport + ? "default" + : "named"; return options; } @@ -513,6 +521,13 @@ async function useInquirer(options: options): Promise { name: "Generate index file", value: "index", checked: options.generationOptions.indexFile + }, + { + name: "Prefer default exports", + value: "defaultExport", + checked: + options.generationOptions.exportType === + "default" } ], message: "Available customizations", @@ -567,6 +582,11 @@ async function useInquirer(options: options): Promise { "constructor" ); options.generationOptions.indexFile = customizations.includes("index"); + options.generationOptions.exportType = customizations.includes( + "defaultExport" + ) + ? "default" + : "named"; if (customizations.includes("namingStrategy")) { const namingStrategyPath = ( diff --git a/src/templates/entity.mst b/src/templates/entity.mst index ce84e6b..2c2c1c9 100644 --- a/src/templates/entity.mst +++ b/src/templates/entity.mst @@ -2,7 +2,7 @@ @Index("{{name}}",[{{#columns}}"{{toPropertyName .}}",{{/columns~}}],{ {{json options}} }) {{/inline}} {{#*inline "Import"}} -import { {{toEntityName .}} } from './{{toFileName .}}' +import {{localImport (toEntityName .)}} from './{{toFileName .}}' {{/inline}} {{#*inline "Column"}} {{#generated}}@PrimaryGeneratedColumn({ type:"{{type}}", {{/generated}}{{^generated}}@Column("{{type}}",{ {{#primary}}primary:{{primary}},{{/primary}}{{/generated}}{{json options}}{{#default}},default: {{.}},{{/default}} }) @@ -33,7 +33,7 @@ import { {{toEntityName .}} } from './{{toFileName .}}' {{#*inline "Entity"}} {{#indices}}{{> Index}}{{/indices~}} @Entity("{{sqlName}}"{{#schema}} ,{schema:"{{.}}"{{#if ../database}}, database:"{{../database}}"{{/if}} } {{/schema}}) -export class {{toEntityName tscName}}{{#activeRecord}} extends BaseEntity{{/activeRecord}} { +export {{defaultExport}} class {{toEntityName tscName}}{{#activeRecord}} extends BaseEntity{{/activeRecord}} { {{#columns}}{{> Column}}{{/columns~}} {{#relations}}{{> Relation}}{{/relations~}} diff --git a/src/templates/index.mst b/src/templates/index.mst index 928dd19..4c7d5fe 100644 --- a/src/templates/index.mst +++ b/src/templates/index.mst @@ -1,3 +1,5 @@ {{#entities~}} -export { {{toEntityName tscName}} } from './{{toFileName tscName}}' -{{/entities~}} \ No newline at end of file +import {{localImport (toEntityName tscName)}} from './{{toFileName tscName}}' +{{/entities}} + +export { {{#entities}}{{toEntityName tscName}},{{/entities~}} } diff --git a/test/modelCustomization/modelCustomization.test.ts b/test/modelCustomization/modelCustomization.test.ts index acbde5f..1f6cd47 100644 --- a/test/modelCustomization/modelCustomization.test.ts +++ b/test/modelCustomization/modelCustomization.test.ts @@ -666,7 +666,7 @@ describe("Model customization phase", async () => { }) }) describe("index file generation", () => { - it("enabled", async () => { + it("named export", async () => { const data = generateSampleData(); const generationOptions = generateGenerationOptions(); @@ -687,8 +687,36 @@ describe("Model customization phase", async () => { const indexFileContent = fs .readFileSync(path.resolve(filesGenPath, "Index.ts")) .toString(); - expect(indexFileContent).to.contain('export { Post } from "./Post";'); - expect(indexFileContent).to.contain('export { PostAuthor } from "./PostAuthor";'); + expect(indexFileContent).to.contain('import { PostAuthor } from "./PostAuthor'); + expect(indexFileContent).to.contain('import { Post } from "./Post'); + expect(indexFileContent).to.contain('export { PostAuthor, Post }'); + compileGeneratedModel(generationOptions.resultsPath, [""]); + }) + it("default export", async () => { + + const data = generateSampleData(); + const generationOptions = generateGenerationOptions(); + generationOptions.indexFile = true; + generationOptions.exportType = "default" + clearGenerationDir(); + + const customizedModel = modelCustomizationPhase( + data, + generationOptions, + {} + ); + modelGenerationPhase( + getDefaultConnectionOptions(), + generationOptions, + customizedModel + ); + const filesGenPath = path.resolve(resultsPath, "entities"); + const indexFileContent = fs + .readFileSync(path.resolve(filesGenPath, "Index.ts")) + .toString(); + expect(indexFileContent).to.contain('import PostAuthor from "./PostAuthor'); + expect(indexFileContent).to.contain('import Post from "./Post'); + expect(indexFileContent).to.contain('export { PostAuthor, Post }'); compileGeneratedModel(generationOptions.resultsPath, [""]); }) it("disabled", async () => { @@ -713,4 +741,5 @@ describe("Model customization phase", async () => { compileGeneratedModel(generationOptions.resultsPath, [""]); }) }) + });