diff --git a/package-lock.json b/package-lock.json index 379644d..24cd216 100644 --- a/package-lock.json +++ b/package-lock.json @@ -407,60 +407,42 @@ } }, "@types/yargs": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-12.0.1.tgz", - "integrity": "sha512-UVjo2oH79aRNcsDlFlnQ/iJ67Jd7j6uSg7jUJP/RZ/nUjAh5ElmnwlD5K/6eGgETJUgCHkiWn91B8JjXQ6ubAw==", - "dev": true - }, - "@types/yn": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/yn/-/yn-3.1.0.tgz", - "integrity": "sha512-Qs2tU/syFYlALjR3EoT+NcvpMwAd6voSiDxW+c8bhAN1WbzQUnRfWTmttORf4R1WqDUT+dvHKj+llupSxs0O/w==", + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.3.tgz", + "integrity": "sha512-K8/LfZq2duW33XW/tFwEAfnZlqIfVsoyRB3kfXdPXYhl0nfM8mmh7GS0jg7WrX2Dgq/0Ha/pR1PaR+BvmWwjiQ==", "dev": true, "requires": { - "yn": "*" + "@types/yargs-parser": "*" } }, + "@types/yargs-parser": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-13.1.0.tgz", + "integrity": "sha512-gCubfBUZ6KxzoibJ+SCUc/57Ms1jz5NjHe4+dI2krNmU5zCPAphyLJYyTOg06ueIyfj+SaCUqmzun7ImlxDcKg==", + "dev": true + }, "@typescript-eslint/eslint-plugin": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.13.0.tgz", - "integrity": "sha512-WQHCozMnuNADiqMtsNzp96FNox5sOVpU8Xt4meaT4em8lOG1SrOv92/mUbEHQVh90sldKSfcOc/I0FOb/14G1g==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.3.3.tgz", + "integrity": "sha512-12cCbwu5PbQudkq2xCIS/QhB7hCMrsNPXK+vJtqy/zFqtzVkPRGy12O5Yy0gUK086f3VHV/P4a4R4CjMW853pA==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "1.13.0", - "eslint-utils": "^1.3.1", + "@typescript-eslint/experimental-utils": "2.3.3", + "eslint-utils": "^1.4.2", "functional-red-black-tree": "^1.0.1", "regexpp": "^2.0.1", - "tsutils": "^3.7.0" + "tsutils": "^3.17.1" } }, "@typescript-eslint/experimental-utils": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz", - "integrity": "sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.3.3.tgz", + "integrity": "sha512-MQ4jKPMTU1ty4TigJCRKFPye2qyQdH8jzIIkceaHgecKFmkNS1hXPqKiZ+mOehkz6+HcN5Nuvwm+frmWZR9tdg==", "dev": true, "requires": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/typescript-estree": "1.13.0", - "eslint-scope": "^4.0.0" - }, - "dependencies": { - "@typescript-eslint/typescript-estree": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz", - "integrity": "sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw==", - "dev": true, - "requires": { - "lodash.unescape": "4.0.1", - "semver": "5.5.0" - } - }, - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", - "dev": true - } + "@typescript-eslint/typescript-estree": "2.3.3", + "eslint-scope": "^5.0.0" } }, "@typescript-eslint/parser": { @@ -1973,9 +1955,9 @@ } }, "eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", + "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", "dev": true, "requires": { "esrecurse": "^4.1.0", @@ -5850,9 +5832,9 @@ } }, "yn": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", - "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" } } } diff --git a/package.json b/package.json index e8f4970..f10558a 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "typeorm": "^0.2.19", "typescript": "^3.6.4", "yargs": "^14.2.0", - "yn": "^2.0.0" + "yn": "^3.1.1" }, "devDependencies": { "@types/chai": "^4.2.3", @@ -57,9 +57,8 @@ "@types/prettier": "^1.18.3", "@types/sinon": "^7.5.0", "@types/sqlite3": "^3.1.5", - "@types/yargs": "^12.0.1", - "@types/yn": "^3.1.0", - "@typescript-eslint/eslint-plugin": "^1.13.0", + "@types/yargs": "^13.0.3", + "@typescript-eslint/eslint-plugin": "^2.3.3", "@typescript-eslint/parser": "^2.3.3", "@typescript-eslint/typescript-estree": "^2.3.3", "chai": "^4.2.0", diff --git a/src/Engine.ts b/src/Engine.ts index 4f0e7dc..cd9289a 100644 --- a/src/Engine.ts +++ b/src/Engine.ts @@ -1,9 +1,3 @@ -import * as Handlebars from "handlebars"; -import * as Prettier from "prettier"; -import { DataTypeDefaults } from "typeorm/driver/types/DataTypeDefaults"; -import * as changeCase from "change-case"; -import * as fs from "fs"; -import * as path from "path"; import * as TomgUtils from "./Utils"; import AbstractDriver from "./drivers/AbstractDriver"; import MssqlDriver from "./drivers/MssqlDriver"; @@ -14,10 +8,8 @@ import PostgresDriver from "./drivers/PostgresDriver"; import MysqlDriver from "./drivers/MysqlDriver"; import OracleDriver from "./drivers/OracleDriver"; import SqliteDriver from "./drivers/SqliteDriver"; -import NamingStrategy from "./NamingStrategy"; -import AbstractNamingStrategy from "./AbstractNamingStrategy"; -import { Entity } from "./models/Entity"; -import { Relation } from "./models/Relation"; +import modelCustomizationPhase from "./ModelCustomization"; +import modelGenerationPhase from "./ModelGeneration"; export function createDriver(driverName: string): AbstractDriver { switch (driverName) { @@ -70,382 +62,3 @@ export async function dataCollectionPhase( ) { return driver.GetDataFromServer(connectionOptions, generationOptions); } - -export function modelCustomizationPhase( - dbModel: Entity[], - generationOptions: IGenerationOptions, - defaultValues: DataTypeDefaults -) { - let namingStrategy: AbstractNamingStrategy; - if ( - generationOptions.customNamingStrategyPath && - generationOptions.customNamingStrategyPath !== "" - ) { - // eslint-disable-next-line global-require, import/no-dynamic-require, @typescript-eslint/no-var-requires - const req = require(generationOptions.customNamingStrategyPath); - namingStrategy = new req.NamingStrategy(); - } else { - namingStrategy = new NamingStrategy(); - } - let retVal = applyNamingStrategy(namingStrategy, dbModel); - retVal = addImportsAndGenerationOptions(retVal, generationOptions); - retVal = removeColumnDefaultProperties(retVal, defaultValues); - return retVal; -} -function removeColumnDefaultProperties( - dbModel: Entity[], - defaultValues: DataTypeDefaults -) { - if (!defaultValues) { - return dbModel; - } - dbModel.forEach(entity => { - entity.columns.forEach(column => { - const defVal = defaultValues[column.tscType]; - if (defVal) { - if ( - column.options.length && - defVal.length && - column.options.length === defVal.length - ) { - column.options.length = undefined; - } - if ( - column.options.precision && - defVal.precision && - column.options.precision === defVal.precision && - column.options.scale && - defVal.scale && - column.options.scale === defVal.scale - ) { - column.options.precision = undefined; - column.options.scale = undefined; - } - if ( - column.options.width && - defVal.width && - column.options.width === defVal.width - ) { - column.options.width = undefined; - } - } - }); - }); - return dbModel; -} -function addImportsAndGenerationOptions( - dbModel: Entity[], - generationOptions: IGenerationOptions -) { - dbModel.forEach(entity => { - entity.relations.forEach(relation => { - if (generationOptions.lazy) { - if (!relation.relationOptions) { - relation.relationOptions = {}; - } - relation.relationOptions.lazy = true; - } - }); - if (generationOptions.skipSchema) { - entity.schema = undefined; - entity.database = undefined; - } - }); - return dbModel; -} - -export function modelGenerationPhase( - connectionOptions: IConnectionOptions, - generationOptions: IGenerationOptions, - databaseModel: Entity[] -) { - 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); - } - let entitiesPath = resultPath; - if (!generationOptions.noConfigs) { - createTsConfigFile(resultPath); - createTypeOrmConfig(resultPath, connectionOptions); - entitiesPath = path.resolve(resultPath, "./entities"); - if (!fs.existsSync(entitiesPath)) { - fs.mkdirSync(entitiesPath); - } - } - const compliedTemplate = Handlebars.compile(template, { - noEscape: true - }); - databaseModel.forEach(element => { - let casedFileName = ""; - switch (generationOptions.convertCaseFile) { - case "camel": - casedFileName = changeCase.camelCase(element.tscName); - break; - case "param": - casedFileName = changeCase.paramCase(element.tscName); - break; - case "pascal": - casedFileName = changeCase.pascalCase(element.tscName); - break; - case "none": - casedFileName = element.tscName; - break; - default: - throw new Error("Unknown case style"); - } - const resultFilePath = path.resolve( - entitiesPath, - `${casedFileName}.ts` - ); - const rendered = compliedTemplate(element); - const formatted = Prettier.format(rendered, { parser: "typescript" }); - fs.writeFileSync(resultFilePath, formatted, { - encoding: "UTF-8", - flag: "w" - }); - }); -} - -function createHandlebarsHelpers(generationOptions: IGenerationOptions) { - Handlebars.registerHelper("json", context => { - const json = JSON.stringify(context); - const withoutQuotes = json.replace(/"([^(")"]+)":/g, "$1:"); - return withoutQuotes.slice(1, withoutQuotes.length - 1); - }); - Handlebars.registerHelper("toEntityName", str => { - let retStr = ""; - switch (generationOptions.convertCaseEntity) { - case "camel": - retStr = changeCase.camelCase(str); - break; - case "pascal": - retStr = changeCase.pascalCase(str); - break; - case "none": - retStr = str; - break; - default: - throw new Error("Unknown case style"); - } - return retStr; - }); - Handlebars.registerHelper("toFileName", str => { - let retStr = ""; - switch (generationOptions.convertCaseFile) { - case "camel": - retStr = changeCase.camelCase(str); - break; - case "param": - retStr = changeCase.paramCase(str); - break; - case "pascal": - retStr = changeCase.pascalCase(str); - break; - case "none": - retStr = str; - break; - default: - throw new Error("Unknown case style"); - } - return retStr; - }); - Handlebars.registerHelper("printPropertyVisibility", () => - // TODO: - generationOptions.propertyVisibility !== "none" - ? `${generationOptions.propertyVisibility} ` - : "" - ); - Handlebars.registerHelper("toPropertyName", str => { - let retStr = ""; - switch (generationOptions.convertCaseProperty) { - case "camel": - retStr = changeCase.camelCase(str); - break; - case "pascal": - retStr = changeCase.pascalCase(str); - break; - case "none": - retStr = str; - break; - default: - throw new Error("Unknown case style"); - } - return retStr; - }); - Handlebars.registerHelper( - "toRelation", - (entityType: string, relationType: Relation["relationType"]) => { - let retVal = entityType; - if (relationType === "ManyToMany" || relationType === "OneToMany") { - retVal = `${retVal}[]`; - } - if (generationOptions.lazy) { - retVal = `Promise<${retVal}>`; - } - return retVal; - } - ); - Handlebars.registerHelper("strictMode", () => - // TODO: - generationOptions.strictMode ? generationOptions.strictMode : "" - ); - Handlebars.registerHelper({ - and: (v1, v2) => v1 && v2, - eq: (v1, v2) => v1 === v2, - gt: (v1, v2) => v1 > v2, - gte: (v1, v2) => v1 >= v2, - lt: (v1, v2) => v1 < v2, - lte: (v1, v2) => v1 <= v2, - ne: (v1, v2) => v1 !== v2, - or: (v1, v2) => v1 || v2 - }); -} - -function createTsConfigFile(outputPath: string) { - const templatePath = path.resolve(__dirname, "templates", "tsconfig.mst"); - const template = fs.readFileSync(templatePath, "UTF-8"); - const compliedTemplate = Handlebars.compile(template, { - noEscape: true - }); - const rendered = compliedTemplate({}); - const formatted = Prettier.format(rendered, { parser: "json" }); - const resultFilePath = path.resolve(outputPath, "tsconfig.json"); - fs.writeFileSync(resultFilePath, formatted, { - encoding: "UTF-8", - flag: "w" - }); -} -function createTypeOrmConfig( - outputPath: string, - connectionOptions: IConnectionOptions -) { - const templatePath = path.resolve(__dirname, "templates", "ormconfig.mst"); - const template = fs.readFileSync(templatePath, "UTF-8"); - const compliedTemplate = Handlebars.compile(template, { - noEscape: true - }); - const rendered = compliedTemplate(connectionOptions); - const formatted = Prettier.format(rendered, { parser: "json" }); - const resultFilePath = path.resolve(outputPath, "ormconfig.json"); - fs.writeFileSync(resultFilePath, formatted, { - encoding: "UTF-8", - flag: "w" - }); -} -function applyNamingStrategy( - namingStrategy: AbstractNamingStrategy, - dbModel: Entity[] -) { - let retVal = changeRelationNames(dbModel); - retVal = changeRelationIdNames(retVal); - retVal = changeEntityNames(retVal); - retVal = changeColumnNames(retVal); - return retVal; - - function changeRelationIdNames(model: Entity[]) { - model.forEach(entity => { - entity.relationIds.forEach(relationId => { - const oldName = relationId.fieldName; - const relation = entity.relations.find( - v => v.fieldName === relationId.relationField - )!; - let newName = namingStrategy.relationIdName( - relationId, - relation, - entity - ); - newName = TomgUtils.findNameForNewField( - newName, - entity, - oldName - ); - entity.indices.forEach(index => { - index.columns = index.columns.map(column2 => - column2 === oldName ? newName : column2 - ); - }); - - relationId.fieldName = newName; - }); - }); - return dbModel; - } - - function changeRelationNames(model: Entity[]) { - model.forEach(entity => { - entity.relations.forEach(relation => { - const oldName = relation.fieldName; - let newName = namingStrategy.relationName(relation, entity); - newName = TomgUtils.findNameForNewField( - newName, - entity, - oldName - ); - - const relatedEntity = model.find( - v => v.tscName === relation.relatedTable - )!; - const relation2 = relatedEntity.relations.find( - v => v.fieldName === relation.relatedField - )!; - - entity.relationIds - .filter(v => v.relationField === oldName) - .forEach(v => { - v.relationField = newName; - }); - - relation.fieldName = newName; - relation2.relatedField = newName; - - if (relation.relationOptions) { - entity.indices.forEach(ind => { - ind.columns.map(column2 => - column2 === oldName ? newName : column2 - ); - }); - } - }); - }); - return dbModel; - } - - function changeColumnNames(model: Entity[]) { - model.forEach(entity => { - entity.columns.forEach(column => { - const oldName = column.tscName; - let newName = namingStrategy.columnName(column.tscName, column); - newName = TomgUtils.findNameForNewField( - newName, - entity, - oldName - ); - entity.indices.forEach(index => { - index.columns = index.columns.map(column2 => - column2 === oldName ? newName : column2 - ); - }); - - column.tscName = newName; - }); - }); - return model; - } - function changeEntityNames(entities: Entity[]) { - entities.forEach(entity => { - const newName = namingStrategy.entityName(entity.tscName, entity); - entities.forEach(entity2 => { - entity2.relations.forEach(relation => { - if (relation.relatedTable === entity.tscName) { - relation.relatedTable = newName; - } - }); - }); - entity.tscName = newName; - }); - return entities; - } -} diff --git a/src/IConnectionOptions.ts b/src/IConnectionOptions.ts index 3cab302..c280478 100644 --- a/src/IConnectionOptions.ts +++ b/src/IConnectionOptions.ts @@ -1,19 +1,19 @@ export default class IConnectionOptions { - public host: string = ""; + public host = ""; - public port: number = 0; + public port = 0; - public databaseName: string = ""; + public databaseName = ""; - public user: string = ""; + public user = ""; - public password: string = ""; + public password = ""; - public databaseType: string = ""; + public databaseType = ""; - public schemaName: string = ""; + public schemaName = ""; - public ssl: boolean = false; + public ssl = false; public timeout?: number; } diff --git a/src/IGenerationOptions.ts b/src/IGenerationOptions.ts index 9b64956..23180f6 100644 --- a/src/IGenerationOptions.ts +++ b/src/IGenerationOptions.ts @@ -1,7 +1,7 @@ export default class IGenerationOptions { - public resultsPath: string = ""; + public resultsPath = ""; - public noConfigs: boolean = false; + public noConfigs = false; public convertCaseFile: "pascal" | "param" | "camel" | "none" = "none"; @@ -12,17 +12,17 @@ export default class IGenerationOptions { public propertyVisibility: "public" | "protected" | "private" | "none" = "none"; - public lazy: boolean = false; + public lazy = false; - public activeRecord: boolean = false; + public activeRecord = false; - public generateConstructor: boolean = false; + public generateConstructor = false; - public customNamingStrategyPath: string = ""; + public customNamingStrategyPath = ""; - public relationIds: boolean = false; + public relationIds = false; public strictMode: false | "?" | "!" = false; - public skipSchema: boolean = false; + public skipSchema = false; } diff --git a/src/ModelCustomization.ts b/src/ModelCustomization.ts new file mode 100644 index 0000000..1bc2bea --- /dev/null +++ b/src/ModelCustomization.ts @@ -0,0 +1,204 @@ +import { DataTypeDefaults } from "typeorm/driver/types/DataTypeDefaults"; +import { Entity } from "./models/Entity"; +import IGenerationOptions from "./IGenerationOptions"; +import AbstractNamingStrategy from "./AbstractNamingStrategy"; +import NamingStrategy from "./NamingStrategy"; +import * as TomgUtils from "./Utils"; + +export default function modelCustomizationPhase( + dbModel: Entity[], + generationOptions: IGenerationOptions, + defaultValues: DataTypeDefaults +) { + let namingStrategy: AbstractNamingStrategy; + if ( + generationOptions.customNamingStrategyPath && + generationOptions.customNamingStrategyPath !== "" + ) { + // eslint-disable-next-line global-require, import/no-dynamic-require, @typescript-eslint/no-var-requires + const req = require(generationOptions.customNamingStrategyPath); + namingStrategy = new req.NamingStrategy(); + } else { + namingStrategy = new NamingStrategy(); + } + let retVal = applyNamingStrategy(namingStrategy, dbModel); + retVal = addImportsAndGenerationOptions(retVal, generationOptions); + retVal = removeColumnDefaultProperties(retVal, defaultValues); + return retVal; +} +function removeColumnDefaultProperties( + dbModel: Entity[], + defaultValues: DataTypeDefaults +) { + if (!defaultValues) { + return dbModel; + } + dbModel.forEach(entity => { + entity.columns.forEach(column => { + const defVal = defaultValues[column.tscType]; + if (defVal) { + if ( + column.options.length && + defVal.length && + column.options.length === defVal.length + ) { + column.options.length = undefined; + } + if ( + column.options.precision && + defVal.precision && + column.options.precision === defVal.precision && + column.options.scale && + defVal.scale && + column.options.scale === defVal.scale + ) { + column.options.precision = undefined; + column.options.scale = undefined; + } + if ( + column.options.width && + defVal.width && + column.options.width === defVal.width + ) { + column.options.width = undefined; + } + } + }); + }); + return dbModel; +} +function addImportsAndGenerationOptions( + dbModel: Entity[], + generationOptions: IGenerationOptions +) { + dbModel.forEach(entity => { + entity.relations.forEach(relation => { + if (generationOptions.lazy) { + if (!relation.relationOptions) { + relation.relationOptions = {}; + } + relation.relationOptions.lazy = true; + } + }); + if (generationOptions.skipSchema) { + entity.schema = undefined; + entity.database = undefined; + } + }); + return dbModel; +} + +function applyNamingStrategy( + namingStrategy: AbstractNamingStrategy, + dbModel: Entity[] +) { + let retVal = changeRelationNames(dbModel); + retVal = changeRelationIdNames(retVal); + retVal = changeEntityNames(retVal); + retVal = changeColumnNames(retVal); + return retVal; + + function changeRelationIdNames(model: Entity[]) { + model.forEach(entity => { + entity.relationIds.forEach(relationId => { + const oldName = relationId.fieldName; + const relation = entity.relations.find( + v => v.fieldName === relationId.relationField + )!; + let newName = namingStrategy.relationIdName( + relationId, + relation, + entity + ); + newName = TomgUtils.findNameForNewField( + newName, + entity, + oldName + ); + entity.indices.forEach(index => { + index.columns = index.columns.map(column2 => + column2 === oldName ? newName : column2 + ); + }); + + relationId.fieldName = newName; + }); + }); + return dbModel; + } + + function changeRelationNames(model: Entity[]) { + model.forEach(entity => { + entity.relations.forEach(relation => { + const oldName = relation.fieldName; + let newName = namingStrategy.relationName(relation, entity); + newName = TomgUtils.findNameForNewField( + newName, + entity, + oldName + ); + + const relatedEntity = model.find( + v => v.tscName === relation.relatedTable + )!; + const relation2 = relatedEntity.relations.find( + v => v.fieldName === relation.relatedField + )!; + + entity.relationIds + .filter(v => v.relationField === oldName) + .forEach(v => { + v.relationField = newName; + }); + + relation.fieldName = newName; + relation2.relatedField = newName; + + if (relation.relationOptions) { + entity.indices.forEach(ind => { + ind.columns.map(column2 => + column2 === oldName ? newName : column2 + ); + }); + } + }); + }); + return dbModel; + } + + function changeColumnNames(model: Entity[]) { + model.forEach(entity => { + entity.columns.forEach(column => { + const oldName = column.tscName; + let newName = namingStrategy.columnName(column.tscName, column); + newName = TomgUtils.findNameForNewField( + newName, + entity, + oldName + ); + entity.indices.forEach(index => { + index.columns = index.columns.map(column2 => + column2 === oldName ? newName : column2 + ); + }); + + column.tscName = newName; + }); + }); + return model; + } + function changeEntityNames(entities: Entity[]) { + entities.forEach(entity => { + const newName = namingStrategy.entityName(entity.tscName, entity); + entities.forEach(entity2 => { + entity2.relations.forEach(relation => { + if (relation.relatedTable === entity.tscName) { + relation.relatedTable = newName; + } + }); + }); + entity.tscName = newName; + }); + return entities; + } +} diff --git a/src/ModelGeneration.ts b/src/ModelGeneration.ts new file mode 100644 index 0000000..6f0b27e --- /dev/null +++ b/src/ModelGeneration.ts @@ -0,0 +1,191 @@ +import * as Handlebars from "handlebars"; +import * as Prettier from "prettier"; +import * as changeCase from "change-case"; +import * as fs from "fs"; +import * as path from "path"; +import IConnectionOptions from "./IConnectionOptions"; +import IGenerationOptions from "./IGenerationOptions"; +import { Entity } from "./models/Entity"; +import { Relation } from "./models/Relation"; + +export default function modelGenerationPhase( + connectionOptions: IConnectionOptions, + generationOptions: IGenerationOptions, + databaseModel: Entity[] +) { + 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); + } + let entitiesPath = resultPath; + if (!generationOptions.noConfigs) { + createTsConfigFile(resultPath); + createTypeOrmConfig(resultPath, connectionOptions); + entitiesPath = path.resolve(resultPath, "./entities"); + if (!fs.existsSync(entitiesPath)) { + fs.mkdirSync(entitiesPath); + } + } + const compliedTemplate = Handlebars.compile(template, { + noEscape: true + }); + databaseModel.forEach(element => { + let casedFileName = ""; + switch (generationOptions.convertCaseFile) { + case "camel": + casedFileName = changeCase.camelCase(element.tscName); + break; + case "param": + casedFileName = changeCase.paramCase(element.tscName); + break; + case "pascal": + casedFileName = changeCase.pascalCase(element.tscName); + break; + case "none": + casedFileName = element.tscName; + break; + default: + throw new Error("Unknown case style"); + } + const resultFilePath = path.resolve( + entitiesPath, + `${casedFileName}.ts` + ); + const rendered = compliedTemplate(element); + const formatted = Prettier.format(rendered, { parser: "typescript" }); + fs.writeFileSync(resultFilePath, formatted, { + encoding: "UTF-8", + flag: "w" + }); + }); +} + +function createHandlebarsHelpers(generationOptions: IGenerationOptions) { + Handlebars.registerHelper("json", context => { + const json = JSON.stringify(context); + const withoutQuotes = json.replace(/"([^(")"]+)":/g, "$1:"); + return withoutQuotes.slice(1, withoutQuotes.length - 1); + }); + Handlebars.registerHelper("toEntityName", str => { + let retStr = ""; + switch (generationOptions.convertCaseEntity) { + case "camel": + retStr = changeCase.camelCase(str); + break; + case "pascal": + retStr = changeCase.pascalCase(str); + break; + case "none": + retStr = str; + break; + default: + throw new Error("Unknown case style"); + } + return retStr; + }); + Handlebars.registerHelper("toFileName", str => { + let retStr = ""; + switch (generationOptions.convertCaseFile) { + case "camel": + retStr = changeCase.camelCase(str); + break; + case "param": + retStr = changeCase.paramCase(str); + break; + case "pascal": + retStr = changeCase.pascalCase(str); + break; + case "none": + retStr = str; + break; + default: + throw new Error("Unknown case style"); + } + return retStr; + }); + Handlebars.registerHelper("printPropertyVisibility", () => + // TODO: + generationOptions.propertyVisibility !== "none" + ? `${generationOptions.propertyVisibility} ` + : "" + ); + Handlebars.registerHelper("toPropertyName", str => { + let retStr = ""; + switch (generationOptions.convertCaseProperty) { + case "camel": + retStr = changeCase.camelCase(str); + break; + case "pascal": + retStr = changeCase.pascalCase(str); + break; + case "none": + retStr = str; + break; + default: + throw new Error("Unknown case style"); + } + return retStr; + }); + Handlebars.registerHelper( + "toRelation", + (entityType: string, relationType: Relation["relationType"]) => { + let retVal = entityType; + if (relationType === "ManyToMany" || relationType === "OneToMany") { + retVal = `${retVal}[]`; + } + if (generationOptions.lazy) { + retVal = `Promise<${retVal}>`; + } + return retVal; + } + ); + Handlebars.registerHelper("strictMode", () => + // TODO: + generationOptions.strictMode ? generationOptions.strictMode : "" + ); + Handlebars.registerHelper({ + and: (v1, v2) => v1 && v2, + eq: (v1, v2) => v1 === v2, + gt: (v1, v2) => v1 > v2, + gte: (v1, v2) => v1 >= v2, + lt: (v1, v2) => v1 < v2, + lte: (v1, v2) => v1 <= v2, + ne: (v1, v2) => v1 !== v2, + or: (v1, v2) => v1 || v2 + }); +} + +function createTsConfigFile(outputPath: string) { + const templatePath = path.resolve(__dirname, "templates", "tsconfig.mst"); + const template = fs.readFileSync(templatePath, "UTF-8"); + const compliedTemplate = Handlebars.compile(template, { + noEscape: true + }); + const rendered = compliedTemplate({}); + const formatted = Prettier.format(rendered, { parser: "json" }); + const resultFilePath = path.resolve(outputPath, "tsconfig.json"); + fs.writeFileSync(resultFilePath, formatted, { + encoding: "UTF-8", + flag: "w" + }); +} +function createTypeOrmConfig( + outputPath: string, + connectionOptions: IConnectionOptions +) { + const templatePath = path.resolve(__dirname, "templates", "ormconfig.mst"); + const template = fs.readFileSync(templatePath, "UTF-8"); + const compliedTemplate = Handlebars.compile(template, { + noEscape: true + }); + const rendered = compliedTemplate(connectionOptions); + const formatted = Prettier.format(rendered, { parser: "json" }); + const resultFilePath = path.resolve(outputPath, "ormconfig.json"); + fs.writeFileSync(resultFilePath, formatted, { + encoding: "UTF-8", + flag: "w" + }); +} diff --git a/src/Utils.ts b/src/Utils.ts index bd10f3b..700db61 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -3,7 +3,7 @@ import { Entity } from "./models/Entity"; export function LogError( errText: string, - isABug: boolean = true, + isABug = true, passedError?: string | ErrorConstructor ) { let errObject = passedError; diff --git a/src/drivers/OracleDriver.ts b/src/drivers/OracleDriver.ts index 88f99e9..38cf79b 100644 --- a/src/drivers/OracleDriver.ts +++ b/src/drivers/OracleDriver.ts @@ -350,11 +350,10 @@ export default class OracleDriver extends AbstractDriver { user: connectionOptions.user }; } - const that = this; const promise = new Promise((resolve, reject) => { this.Oracle.getConnection(config, (err, connection) => { if (!err) { - that.Connection = connection; + this.Connection = connection; resolve(true); } else { TomgUtils.LogError( diff --git a/src/index.ts b/src/index.ts index c3a37aa..ba03a04 100644 --- a/src/index.ts +++ b/src/index.ts @@ -51,33 +51,38 @@ async function CliLogic() { function GetUtilParametersByArgs() { const { argv } = Yargs.usage( - "Usage: typeorm-model-generator -h -d -p [port] -u -x [password] -e [engine]\nYou can also run program without specyfiying any parameters." - ) - .option("h", { + "Usage: typeorm-model-generator -h -d -p [port] -u -x [password] -e [engine]\nYou can also run program without specifying any parameters." + ).options({ + h: { alias: "host", + string: true, default: "127.0.0.1", describe: "IP address/Hostname for database server" - }) - .option("d", { + }, + d: { alias: "database", + string: true, demand: true, describe: "Database name(or path for sqlite). You can pass multiple values separated by comma." - }) - .option("u", { + }, + u: { alias: "user", + string: true, describe: "Username for database server" - }) - .option("x", { + }, + x: { alias: "pass", + string: true, default: "", describe: "Password for database server" - }) - .option("p", { + }, + p: { + number: true, alias: "port", describe: "Port number for database server" - }) - .option("e", { + }, + e: { alias: "engine", choices: [ "mssql", @@ -89,94 +94,95 @@ function GetUtilParametersByArgs() { ], default: "mssql", describe: "Database engine" - }) - .option("o", { + }, + o: { alias: "output", default: path.resolve(process.cwd(), "output"), describe: "Where to place generated models" - }) - .option("s", { + }, + s: { alias: "schema", + string: true, describe: "Schema name to create model from. Only for mssql and postgres. You can pass multiple values separated by comma eg. -s scheme1,scheme2,scheme3" - }) - .option("ssl", { + }, + ssl: { boolean: true, default: false - }) - .option("noConfig", { + }, + noConfig: { boolean: true, default: false, describe: `Doesn't create tsconfig.json and ormconfig.json` - }) - .option("cf", { + }, + cf: { alias: "case-file", choices: ["pascal", "param", "camel", "none"], default: "pascal", describe: "Convert file names to specified case" - }) - .option("ce", { + }, + ce: { alias: "case-entity", choices: ["pascal", "camel", "none"], default: "pascal", describe: "Convert class names to specified case" - }) - .option("cp", { + }, + cp: { alias: "case-property", choices: ["pascal", "camel", "none"], default: "camel", describe: "Convert property names to specified case" - }) - .option("pv", { + }, + pv: { alias: "property-visibility", choices: ["public", "protected", "private", "none"], default: "none", describe: "Defines which visibility should have the generated property" - }) - .option("lazy", { + }, + lazy: { boolean: true, default: false, describe: "Generate lazy relations" - }) - .option("a", { + }, + a: { alias: "active-record", boolean: true, default: false, describe: "Use ActiveRecord syntax for generated models" - }) - .option("namingStrategy", { - describe: "Use custom naming strategy" - }) - .option("relationIds", { + }, + namingStrategy: { + describe: "Use custom naming strategy", + string: true + }, + relationIds: { boolean: true, default: false, describe: "Generate RelationId fields" - }) - .option("skipSchema", { + }, + skipSchema: { boolean: true, default: false, describe: "Omits schema identifier in generated entities" - }) - .option("generateConstructor", { + }, + generateConstructor: { boolean: true, default: false, describe: "Generate constructor allowing partial initialization" - }) - .option("strictMode", { + }, + strictMode: { choices: ["none", "?", "!"], default: "none", describe: "Mark fields as optional(?) or non-null(!)" - }) - .option("timeout", { + }, + timeout: { describe: "SQL Query timeout(ms)", number: true - }); + } + }); const driver = createDriver(argv.e); - const { standardPort } = driver; - const { standardSchema } = driver; - const standardUser = driver.standardPort; + const { standardPort, standardSchema, standardUser } = driver; let namingStrategyPath: string; if (argv.namingStrategy && argv.namingStrategy !== "") { namingStrategyPath = argv.namingStrategy; @@ -184,11 +190,11 @@ function GetUtilParametersByArgs() { namingStrategyPath = ""; } const connectionOptions: IConnectionOptions = new IConnectionOptions(); - connectionOptions.databaseName = argv.d ? argv.d.toString() : null; + connectionOptions.databaseName = argv.d; connectionOptions.databaseType = argv.e; connectionOptions.host = argv.h; - connectionOptions.password = argv.x ? argv.x.toString() : null; - connectionOptions.port = parseInt(argv.p, 10) || standardPort; + connectionOptions.password = argv.x; + connectionOptions.port = argv.p || standardPort; connectionOptions.schemaName = argv.s ? argv.s.toString() : standardSchema; connectionOptions.ssl = argv.ssl; connectionOptions.timeout = argv.timeout; @@ -196,18 +202,20 @@ function GetUtilParametersByArgs() { const generationOptions: IGenerationOptions = new IGenerationOptions(); generationOptions.activeRecord = argv.a; generationOptions.generateConstructor = argv.generateConstructor; - generationOptions.convertCaseEntity = argv.ce; - generationOptions.convertCaseFile = argv.cf; - generationOptions.convertCaseProperty = argv.cp; + generationOptions.convertCaseEntity = argv.ce as IGenerationOptions["convertCaseEntity"]; + generationOptions.convertCaseFile = argv.cf as IGenerationOptions["convertCaseFile"]; + generationOptions.convertCaseProperty = argv.cp as IGenerationOptions["convertCaseProperty"]; generationOptions.lazy = argv.lazy; generationOptions.customNamingStrategyPath = namingStrategyPath; generationOptions.noConfigs = argv.noConfig; - generationOptions.propertyVisibility = argv.pv; + generationOptions.propertyVisibility = argv.pv as IGenerationOptions["propertyVisibility"]; generationOptions.relationIds = argv.relationIds; generationOptions.skipSchema = argv.skipSchema; - generationOptions.resultsPath = argv.o ? argv.o.toString() : null; + generationOptions.resultsPath = argv.o; generationOptions.strictMode = - argv.strictMode === "none" ? false : argv.strictMode; + argv.strictMode === "none" + ? false + : (argv.strictMode as IGenerationOptions["strictMode"]); return { driver, connectionOptions, generationOptions }; } diff --git a/test/integration/runTestsFromPath.test.ts b/test/integration/runTestsFromPath.test.ts index 240844d..e86fa39 100644 --- a/test/integration/runTestsFromPath.test.ts +++ b/test/integration/runTestsFromPath.test.ts @@ -1,32 +1,28 @@ import "reflect-metadata"; -import { expect } from "chai"; +import * as chai from "chai"; import * as ts from "typescript"; +import * as fs from "fs-extra"; +import * as path from "path"; +import * as chaiSubset from "chai-subset"; +import yn from "yn"; import EntityFileToJson from "../utils/EntityFileToJson"; -import { - createDriver, - dataCollectionPhase, - modelCustomizationPhase, - modelGenerationPhase -} from "../../src/Engine"; +import { createDriver, dataCollectionPhase } from "../../src/Engine"; import * as GTU from "../utils/GeneralTestUtils"; import { Entity } from "../../src/models/Entity"; import IConnectionOptions from "../../src/IConnectionOptions"; - -import fs = require("fs-extra"); -import path = require("path"); -import chaiSubset = require("chai-subset"); -import chai = require("chai"); -import yn = require("yn"); +import modelCustomizationPhase from "../../src/ModelCustomization"; +import modelGenerationPhase from "../../src/ModelGeneration"; require("dotenv").config(); chai.use(chaiSubset); +const { expect } = chai; it("Column default values", async () => { const testPartialPath = "test/integration/defaultValues"; await runTestsFromPath(testPartialPath, true); }).timeout(); -it("Platform specyfic types", async () => { +it("Platform specific types", async () => { const testPartialPath = "test/integration/entityTypes"; await runTestsFromPath(testPartialPath, true); }); @@ -70,7 +66,7 @@ function runTestForMultipleDrivers( testPartialPath: string ) { it(testName, async () => { - const driversToRun = selectDriversForSpecyficTest(); + const driversToRun = selectDriversForSpecificTest(); const modelGenerationPromises = driversToRun.map(async dbDriver => { const { generationOptions, @@ -120,7 +116,7 @@ function runTestForMultipleDrivers( compileGeneratedModel(path.resolve(process.cwd(), `output`), dbDrivers); }); - function selectDriversForSpecyficTest() { + function selectDriversForSpecificTest() { switch (testName) { case "39": return dbDrivers.filter( @@ -187,7 +183,7 @@ function compareGeneratedFiles(filesOrgPathTS: string, filesGenPath: string) { const filesGen = fs .readdirSync(filesGenPath) .filter(val => val.toString().endsWith(".ts")); - expect(filesOrg, "Errors detected in model comparision").to.be.deep.equal( + expect(filesOrg, "Errors detected in model comparison").to.be.deep.equal( filesGen ); filesOrg.forEach(file => { @@ -275,7 +271,7 @@ async function prepareTestRuns( password: String(process.env.MYSQL_Password), databaseType: "mysql", schemaName: "ignored", - ssl: yn(process.env.MYSQL_SSL) + ssl: yn(process.env.MYSQL_SSL, { default: false }) }; break; case "mariadb": @@ -287,7 +283,7 @@ async function prepareTestRuns( password: String(process.env.MARIADB_Password), databaseType: "mariadb", schemaName: "ignored", - ssl: yn(process.env.MARIADB_SSL) + ssl: yn(process.env.MARIADB_SSL, { default: false }) }; break; diff --git a/test/utils/EntityFileToJson.ts b/test/utils/EntityFileToJson.ts index ba6823b..094a818 100644 --- a/test/utils/EntityFileToJson.ts +++ b/test/utils/EntityFileToJson.ts @@ -17,14 +17,14 @@ class EntityColumn { public relationType: "OneToOne" | "OneToMany" | "ManyToOne" | "ManyToMany"; - public isOwnerOfRelation: boolean = false; + public isOwnerOfRelation = false; } class EntityIndex { public indexName: string; public columnNames: string[] = []; - public isUnique: boolean = false; + public isUnique = false; } export default class EntityFileToJson { diff --git a/test/utils/GeneralTestUtils.ts b/test/utils/GeneralTestUtils.ts index d3d2d1d..3a3976c 100644 --- a/test/utils/GeneralTestUtils.ts +++ b/test/utils/GeneralTestUtils.ts @@ -41,7 +41,7 @@ export async function createMSSQLModels( password: String(process.env.MSSQL_Password), databaseType: "mssql", schemaName: "dbo,sch1,sch2", - ssl: yn(process.env.MSSQL_SSL) + ssl: yn(process.env.MSSQL_SSL, { default: false }) }; await driver.ConnectToServer(connectionOptions); connectionOptions.databaseName = String(process.env.MSSQL_Database); @@ -92,7 +92,7 @@ export async function createPostgresModels( password: String(process.env.POSTGRES_Password), databaseType: "postgres", schemaName: "public,sch1,sch2", - ssl: yn(process.env.POSTGRES_SSL) + ssl: yn(process.env.POSTGRES_SSL, { default: false }) }; await driver.ConnectToServer(connectionOptions); connectionOptions.databaseName = String(process.env.POSTGRES_Database); @@ -176,7 +176,7 @@ export async function createMysqlModels( password: String(process.env.MYSQL_Password), databaseType: "mysql", schemaName: "ignored", - ssl: yn(process.env.MYSQL_SSL) + ssl: yn(process.env.MYSQL_SSL, { default: false }) }; await driver.ConnectToServer(connectionOptions); @@ -218,7 +218,7 @@ export async function createMariaDBModels( password: String(process.env.MARIADB_Password), databaseType: "mariadb", schemaName: "ignored", - ssl: yn(process.env.MARIADB_SSL) + ssl: yn(process.env.MARIADB_SSL, { default: false }) }; await driver.ConnectToServer(connectionOptions); @@ -262,7 +262,7 @@ export async function createOracleDBModels( password: String(process.env.ORACLE_PasswordSys), databaseType: "oracle", schemaName: String(process.env.ORACLE_Username), - ssl: yn(process.env.ORACLE_SSL) + ssl: yn(process.env.ORACLE_SSL, { default: false }) }; await driver.ConnectToServer(connectionOptions); connectionOptions.user = String(process.env.ORACLE_Username); diff --git a/tsconfig.json b/tsconfig.json index bcc555a..1a824ca 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es6", + "target": "es2017", "noImplicitAny": false, "emitDecoratorMetadata": true, "experimentalDecorators": true,