Skip to content
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

[Docs/test]: Output Javascript Migrations instead of TypeScript #7294

Merged
merged 8 commits into from
Feb 24, 2021
21 changes: 19 additions & 2 deletions docs/migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ Once you have a migration to run on production, you can run them using a CLI com
typeorm migration:run
```

**`typeorm migration:create` and `typeorm migration:generate` will create `.ts` files. The `migration:run` and `migration:revert` commands only work on `.js` files. Thus the typescript files need to be compiled before running the commands.** Alternatively you can use `ts-node` in conjunction with `typeorm` to run `.ts` migration files.
**`typeorm migration:create` and `typeorm migration:generate` will create `.ts` files, unless you use the `o` flag (see more in [Generating migrations](#generating-migrations)). The `migration:run` and `migration:revert` commands only work on `.js` files. Thus the typescript files need to be compiled before running the commands.** Alternatively you can use `ts-node` in conjunction with `typeorm` to run `.ts` migration files.

Example with `ts-node`:
```
Expand Down Expand Up @@ -194,8 +194,25 @@ export class PostRefactoringTIMESTAMP implements MigrationInterface {
}
```

Alternatively you can also output your migrations as Javascript files using the `o` (alias for `--outputJs`) flag. This is useful for Javascript only projects in which TypeScript additional packages are not installed. This command, will generate a new migration file `{TIMESTAMP}-PostRefactoring.js` with the following content:

```javascript
const { MigrationInterface, QueryRunner } = require("typeorm");

module.exports = class PostRefactoringTIMESTAMP {

async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "post" ALTER COLUMN "title" RENAME TO "name"`);
}

async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "post" ALTER COLUMN "title" RENAME TO "name"`);
}
}
```

See, you don't need to write the queries on your own.
The rule of thumb for generating migrations is that you generate them after "each" change you made to your models. To apply multi-line formatting to your generated migration queries, use the `p` (alias for `--pretty`) flag.
The rule of thumb for generating migrations is that you generate them after **each** change you made to your models. To apply multi-line formatting to your generated migration queries, use the `p` (alias for `--pretty`) flag.

## Connection option
If you need to run/revert your migrations for another connection rather than the default, use the `-c` (alias for `--connection`) and pass the config name as an argument
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions test/functional/commands/entity/Post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { PrimaryGeneratedColumn, Entity, Column, CreateDateColumn } from "../../../../src";

@Entity()
export class Post {

@PrimaryGeneratedColumn()
id?: number;

@Column()
title: string;

@CreateDateColumn()
readonly createdAt?: Date;
}
122 changes: 122 additions & 0 deletions test/functional/commands/migration-create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import sinon from "sinon";
import { ConnectionOptions, ConnectionOptionsReader, DatabaseType } from "../../../src";
import {
setupTestingConnections,
createTestingConnections,
closeTestingConnections,
reloadTestingDatabases
} from "../../utils/test-utils";
import { CommandUtils } from "../../../src/commands/CommandUtils";
import { MigrationCreateCommand } from "../../../src/commands/MigrationCreateCommand";
import { Post } from "./entity/Post";
import { resultsTemplates } from "./templates/result-templates-create";

describe("commands - migration create", () => {
let connectionOptions: ConnectionOptions[];
let createFileStub: sinon.SinonStub;
let timerStub: sinon.SinonFakeTimers;
let getConnectionOptionsStub: sinon.SinonStub;
let migrationCreateCommand: MigrationCreateCommand;
let connectionOptionsReader: ConnectionOptionsReader;
let baseConnectionOptions: ConnectionOptions;

const enabledDrivers = [
"postgres",
"mssql",
"mysql",
"mariadb",
"sqlite",
"better-sqlite3",
"oracle",
"cockroachdb"
] as DatabaseType[];

// simulate args: `npm run typeorm migration:run -- -n test-migration -d test-directory`
const testHandlerArgs = (options: Record<string, any>) => ({
"$0": "test",
"_": ["test"],
"name": "test-migration",
"dir": "test-directory",
...options
});

before(async () => {
// clean out db from any prior tests in case previous state impacts the generated migrations
const connections = await createTestingConnections({
entities: [],
enabledDrivers
});
await reloadTestingDatabases(connections);
await closeTestingConnections(connections);

connectionOptions = setupTestingConnections({
entities: [Post],
enabledDrivers
});
connectionOptionsReader = new ConnectionOptionsReader();
migrationCreateCommand = new MigrationCreateCommand();
createFileStub = sinon.stub(CommandUtils, "createFile");

timerStub = sinon.useFakeTimers(1610975184784);
});

after(async () => {
timerStub.restore();
createFileStub.restore();
});

afterEach(async () => {
getConnectionOptionsStub.restore();
});

it("should write regular empty migration file when no option is passed", async () => {
for (const connectionOption of connectionOptions) {
createFileStub.resetHistory();

baseConnectionOptions = await connectionOptionsReader.get(connectionOption.name as string);
getConnectionOptionsStub = sinon.stub(ConnectionOptionsReader.prototype, "get").resolves({
...baseConnectionOptions,
entities: [Post]
});

await migrationCreateCommand.handler(testHandlerArgs({
"connection": connectionOption.name
}));

// compare against control test strings in results-templates.ts
sinon.assert.calledWith(
createFileStub,
sinon.match(/test-directory.*test-migration.ts/),
sinon.match(resultsTemplates.control)
);

getConnectionOptionsStub.restore();
}
});

it("should write Javascript empty migration file when option is passed", async () => {
for (const connectionOption of connectionOptions) {
createFileStub.resetHistory();

baseConnectionOptions = await connectionOptionsReader.get(connectionOption.name as string);
getConnectionOptionsStub = sinon.stub(ConnectionOptionsReader.prototype, "get").resolves({
...baseConnectionOptions,
entities: [Post]
});

await migrationCreateCommand.handler(testHandlerArgs({
"connection": connectionOption.name,
"outputJs": true
}));

// compare against control test strings in results-templates.ts
sinon.assert.calledWith(
createFileStub,
sinon.match(/test-directory.*test-migration.js/),
sinon.match(resultsTemplates.javascript)
);

getConnectionOptionsStub.restore();
}
});
});
110 changes: 110 additions & 0 deletions test/functional/commands/migration-generate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import sinon from "sinon";
import { ConnectionOptions, ConnectionOptionsReader, DatabaseType } from "../../../src";
import {
setupTestingConnections,
createTestingConnections,
closeTestingConnections,
reloadTestingDatabases
} from "../../utils/test-utils";
import { CommandUtils } from "../../../src/commands/CommandUtils";
import { MigrationGenerateCommand } from "../../../src/commands/MigrationGenerateCommand";
import { Post } from "./entity/Post";
import { resultsTemplates } from "./templates/result-templates-generate";

describe("commands - migration generate", () => {
let connectionOptions: ConnectionOptions[];
let createFileStub: sinon.SinonStub;
let timerStub: sinon.SinonFakeTimers;
let getConnectionOptionsStub: sinon.SinonStub;
let migrationGenerateCommand: MigrationGenerateCommand;
let connectionOptionsReader: ConnectionOptionsReader;
let baseConnectionOptions: ConnectionOptions;

const enabledDrivers = [
"mysql",
] as DatabaseType[];

// simulate args: `npm run typeorm migration:run -- -n test-migration -d test-directory`
const testHandlerArgs = (options: Record<string, any>) => ({
"$0": "test",
"_": ["test"],
"name": "test-migration",
"dir": "test-directory",
...options
});

before(async () => {
// clean out db from any prior tests in case previous state impacts the generated migrations
const connections = await createTestingConnections({
entities: [],
enabledDrivers
});
await reloadTestingDatabases(connections);
await closeTestingConnections(connections);

connectionOptions = setupTestingConnections({
entities: [Post],
enabledDrivers
});
connectionOptionsReader = new ConnectionOptionsReader();
migrationGenerateCommand = new MigrationGenerateCommand();
createFileStub = sinon.stub(CommandUtils, "createFile");

timerStub = sinon.useFakeTimers(1610975184784);
});

after(async () => {
timerStub.restore();
createFileStub.restore();
});

it("writes regular migration file when no option is passed", async () => {
for (const connectionOption of connectionOptions) {
createFileStub.resetHistory();

baseConnectionOptions = await connectionOptionsReader.get(connectionOption.name as string);
getConnectionOptionsStub = sinon.stub(ConnectionOptionsReader.prototype, "get").resolves({
...baseConnectionOptions,
entities: [Post]
});

await migrationGenerateCommand.handler(testHandlerArgs({
"connection": connectionOption.name
}));

// compare against control test strings in results-templates.ts
sinon.assert.calledWith(
createFileStub,
sinon.match(/test-directory.*test-migration.ts/),
sinon.match(resultsTemplates.control)
);

getConnectionOptionsStub.restore();
}
});

it("writes Javascript printed file when option is passed", async () => {
for (const connectionOption of connectionOptions) {
createFileStub.resetHistory();

baseConnectionOptions = await connectionOptionsReader.get(connectionOption.name as string);
getConnectionOptionsStub = sinon.stub(ConnectionOptionsReader.prototype, "get").resolves({
...baseConnectionOptions,
entities: [Post]
});

await migrationGenerateCommand.handler(testHandlerArgs({
"connection": connectionOption.name,
"outputJs": true
}));

// compare against "pretty" test strings in results-templates.ts
sinon.assert.calledWith(
createFileStub,
sinon.match(/test-directory.*test-migration.js/),
sinon.match(resultsTemplates.javascript)
);
getConnectionOptionsStub.restore();
}
});
});
23 changes: 23 additions & 0 deletions test/functional/commands/templates/result-templates-create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export const resultsTemplates: Record<string, any> = {
control: `import {MigrationInterface, QueryRunner} from "typeorm";

export class testMigration1610975184784 implements MigrationInterface {

public async up(queryRunner: QueryRunner): Promise<void> {
}

public async down(queryRunner: QueryRunner): Promise<void> {
}

}`,
javascript: `const { MigrationInterface, QueryRunner } = require("typeorm");

module.exports = class testMigration1610975184784 {

async up(queryRunner) {
}

async down(queryRunner) {
}
}`
};
29 changes: 29 additions & 0 deletions test/functional/commands/templates/result-templates-generate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export const resultsTemplates: Record<string, any> = {
control: `import {MigrationInterface, QueryRunner} from "typeorm";

export class testMigration1610975184784 implements MigrationInterface {
name = 'testMigration1610975184784'

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query("CREATE TABLE \`post\` (\`id\` int NOT NULL AUTO_INCREMENT, \`title\` varchar(255) NOT NULL, \`createdAt\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), PRIMARY KEY (\`id\`)) ENGINE=InnoDB");
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query("DROP TABLE \`post\`");
}

}`,
javascript: `const { MigrationInterface, QueryRunner } = require("typeorm");

module.exports = class testMigration1610975184784 {
name = 'testMigration1610975184784'

async up(queryRunner) {
await queryRunner.query("CREATE TABLE \`post\` (\`id\` int NOT NULL AUTO_INCREMENT, \`title\` varchar(255) NOT NULL, \`createdAt\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), PRIMARY KEY (\`id\`)) ENGINE=InnoDB");
}

async down(queryRunner) {
await queryRunner.query("DROP TABLE \`post\`");
}
}`
};