-
Notifications
You must be signed in to change notification settings - Fork 1
allow overriding of migration templating #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,12 +1,11 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| import {ConnectionOptionsReader} from "../connection/ConnectionOptionsReader"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| import {CommandUtils} from "./CommandUtils"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| import {createConnection} from "../index"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| import {MysqlDriver} from "../driver/mysql/MysqlDriver"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| import {Query} from "../driver/Query"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| import {SqlInMemory} from "../driver/SqlInMemory"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| import {camelCase} from "../util/StringUtils"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| import * as yargs from "yargs"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| import {AuroraDataApiDriver} from "../driver/aurora-data-api/AuroraDataApiDriver"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| import chalk from "chalk"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { format } from "@sqltools/formatter/lib/sqlFormatter"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Generates a new migration file with sql needs to be executed to update schema. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -34,22 +33,10 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| alias: "dir", | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| describe: "Directory where migration should be created." | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| .option("p", { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| alias: "pretty", | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: "boolean", | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| default: false, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| describe: "Pretty-print generated SQL", | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| .option("f", { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| alias: "config", | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| default: "ormconfig", | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| describe: "Name of the file with connection configuration." | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| .option("o", { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| alias: "outputJs", | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: "boolean", | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| default: false, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| describe: "Generate a migration file on Javascript instead of Typescript", | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -59,8 +46,7 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||
| const timestamp = new Date().getTime(); | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| const extension = args.outputJs ? ".js" : ".ts"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| const filename = timestamp + "-" + args.name + extension; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| const filename = timestamp + "-" + args.name + ".ts"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| let directory = args.dir; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||
| // if directory is not set then try to open tsconfig and find default path there | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -88,47 +74,23 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| logging: false | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||
| const upSqls: string[] = [], downSqls: string[] = []; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||
| const connection = await createConnection(connectionOptions); | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| let sqlInMemory: SqlInMemory; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| const sqlInMemory = await connection.driver.createSchemaBuilder().log(); | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (args.pretty) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| sqlInMemory.upQueries.forEach(upQuery => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| upQuery.query = MigrationGenerateCommand.prettifyQuery(upQuery.query); | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| sqlInMemory.downQueries.forEach(downQuery => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| downQuery.query = MigrationGenerateCommand.prettifyQuery(downQuery.query); | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||
| // mysql is exceptional here because it uses ` character in to escape names in queries, that's why for mysql | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| // we are using simple quoted string instead of template string syntax | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (connection.driver instanceof MysqlDriver || connection.driver instanceof AuroraDataApiDriver) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| sqlInMemory.upQueries.forEach(upQuery => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| upSqls.push(" await queryRunner.query(\"" + upQuery.query.replace(new RegExp(`"`, "g"), `\\"`) + "\"" + MigrationGenerateCommand.queryParams(upQuery.parameters) + ");"); | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| sqlInMemory.downQueries.forEach(downQuery => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| downSqls.push(" await queryRunner.query(\"" + downQuery.query.replace(new RegExp(`"`, "g"), `\\"`) + "\"" + MigrationGenerateCommand.queryParams(downQuery.parameters) + ");"); | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| sqlInMemory.upQueries.forEach(upQuery => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| upSqls.push(" await queryRunner.query(`" + upQuery.query.replace(new RegExp("`", "g"), "\\`") + "`" + MigrationGenerateCommand.queryParams(upQuery.parameters) + ");"); | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| sqlInMemory.downQueries.forEach(downQuery => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| downSqls.push(" await queryRunner.query(`" + downQuery.query.replace(new RegExp("`", "g"), "\\`") + "`" + MigrationGenerateCommand.queryParams(downQuery.parameters) + ");"); | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| sqlInMemory = await connection.driver.createSchemaBuilder().log(); | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| } finally { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| await connection.close(); | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (upSqls.length) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (sqlInMemory.upQueries.length) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (args.name) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| const fileContent = args.outputJs ? | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| MigrationGenerateCommand.getJavascriptTemplate(args.name as any, timestamp, upSqls, downSqls.reverse()) : | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| MigrationGenerateCommand.getTemplate(args.name as any, timestamp, upSqls, downSqls.reverse()); | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| const migrationName = camelCase(args.name as any, true) + timestamp; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| const templater = connection.options.migrationTemplater || defaultMigrationTemplater; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| const fileContent = await templater({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: migrationName, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| upQueries: sqlInMemory.upQueries, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| downQueries: sqlInMemory.downQueries, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| const path = process.cwd() + "/" + (directory ? (directory + "/") : "") + filename; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| await CommandUtils.createFile(path, fileContent); | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -146,76 +108,29 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| process.exit(1); | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ------------------------------------------------------------------------- | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Protected Static Methods | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ------------------------------------------------------------------------- | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Formats query parameters for migration queries if parameters actually exist | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| protected static queryParams(parameters: any[] | undefined): string { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!parameters || !parameters.length) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ""; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||
| return `, ${JSON.stringify(parameters)}`; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Gets contents of the migration file. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| protected static getTemplate(name: string, timestamp: number, upSqls: string[], downSqls: string[]): string { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| const migrationName = `${camelCase(name, true)}${timestamp}`; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||
| return `import {MigrationInterface, QueryRunner} from "typeorm"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| export interface MigrationTemplateArgs { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: string; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| upQueries: Query[]; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| downQueries: Query[]; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| export type MigrationTemplater = (args: MigrationTemplateArgs) => string; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| export async function defaultMigrationTemplater({ name, upQueries, downQueries }: MigrationTemplateArgs): Promise<string> { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| const templateQuery = ({ query, parameters }: Query): string => | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| " await queryRunner.query(`" + query.replace(new RegExp("`", "g"), "\\`") + "`" + (parameters && parameters.length ? `, ${JSON.stringify(parameters)}` : "") + ");"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Check failureCode scanning / CodeQL Incomplete string escaping or encoding High
This does not escape backslash characters in the input.
Copilot AutofixAI about 2 months ago To fix the problem, we must escape both backticks and backslashes in the
Suggested changeset
1
src/commands/MigrationGenerateCommand.ts
Copilot is powered by AI and may make mistakes. Always verify output.
Refresh and try again.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am keeping this escaping consistent with the previous code |
|||||||||||||||||||||||||||||||||||||||||||||||||||||
| return `import { MigrationInterface, QueryRunner } from "typeorm"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| export class ${migrationName} implements MigrationInterface { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| name = '${migrationName}' | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| export class ${name} implements MigrationInterface { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| name = '${name}' | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| public async up(queryRunner: QueryRunner): Promise<void> { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| ${upSqls.join(` | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| `)} | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| ${upQueries.map(templateQuery).join("\n")} | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| public async down(queryRunner: QueryRunner): Promise<void> { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| ${downSqls.join(` | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| `)} | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| `; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Gets contents of the migration file in Javascript. | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| protected static getJavascriptTemplate(name: string, timestamp: number, upSqls: string[], downSqls: string[]): string { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| const migrationName = `${camelCase(name, true)}${timestamp}`; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||
| return `const { MigrationInterface, QueryRunner } = require("typeorm"); | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| module.exports = class ${migrationName} { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| name = '${migrationName}' | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| async up(queryRunner) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| ${upSqls.join(` | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| `)} | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| async down(queryRunner) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| ${downSqls.join(` | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| `)} | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| ${downQueries.map(templateQuery).join("\n")} | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| `; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| protected static prettifyQuery(query: string) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| const formattedQuery = format(query, { indent: " " }); | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "\n" + formattedQuery.replace(/^/gm, " ") + "\n "; | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
one thing I discovered while doing this is that it is possible for typeorm to generate parameterized queries in migrations. I couldn't actually find any examples of these in our codebase (from looking at the typeorm code it seemed like maybe some DDL involving views might use parameters for some reason?). I opted to preserve this in my downstream change just in case there are scenarios where we need parameterization, as it wasn't too involved