Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,10 +302,14 @@ Runs any un-executed committed migrations, as well as the current migration. For
development.

Options:
--help Show help [boolean]
-c, --config Optional path to gmrc file [string] [default: .gmrc[.js|.cjs]]
--shadow Apply migrations to the shadow DB (for development).
--help Show help [boolean]
-c, --config Optional path to gmrc file
[string] [default: .gmrc[.js|.cjs]]
--shadow Apply migrations to the shadow DB (for development).
[boolean] [default: false]
--forceActions Run beforeAllMigrations, afterAllMigrations,
beforeCurrent, and afterCurrent actions even if no
migration was necessary. [boolean] [default: false]
```


Expand Down
70 changes: 69 additions & 1 deletion __tests__/current.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
jest.mock("child_process");
import "./helpers"; // Has side-effects; must come first

import { exec } from "child_process";
import mockFs from "mock-fs";

import { current } from "../src";
import { withClient } from "../src/pg";
import { ParsedSettings, parseSettings } from "../src/settings";
import { ParsedSettings, parseSettings, Settings } from "../src/settings";
import { makeMigrations, resetDb, settings } from "./helpers";

beforeEach(resetDb);
Expand Down Expand Up @@ -134,3 +136,69 @@ it("runs migrations", async () => {
expect(newTables).toEqual(tables);
expect(newEnums).toEqual(enums);
});

it("runs actions when forceActions is set", async () => {
const ACTIONS = {
initial: {
forceActions: false,
currentSql: "",
expectedActions: [
"beforeAllMigrations",
"afterAllMigrations",
"beforeCurrent",
"afterCurrent",
],
},
currentChange: {
forceActions: false,
currentSql: MIGRATION_NOTRX_TEXT,
expectedActions: ["beforeCurrent", "afterCurrent"],
},
noop: {
forceActions: false,
currentSql: MIGRATION_NOTRX_TEXT,
expectedActions: [],
},
forceActions: {
forceActions: true,
currentSql: MIGRATION_NOTRX_TEXT,
expectedActions: [
"beforeAllMigrations",
"afterAllMigrations",
"beforeCurrent",
"afterCurrent",
],
},
} as const;
const settingsWithHooks: Settings = {
...settings,
beforeAllMigrations: [
{ _: "command", command: "echo did_beforeAllMigrations" },
],
afterAllMigrations: [
{ _: "command", command: "echo did_afterAllMigrations" },
],
beforeCurrent: [{ _: "command", command: "echo did_beforeCurrent" }],
afterCurrent: [{ _: "command", command: "echo did_afterCurrent" }],
};
for (const mode of Object.keys(ACTIONS) as Array<keyof typeof ACTIONS>) {
const { forceActions, currentSql, expectedActions } = ACTIONS[mode];

mockFs({
[`migrations/committed/000001.sql`]: MIGRATION_1_COMMITTED,
[`migrations/committed/000002.sql`]: MIGRATION_ENUM_COMMITTED, // Creates enum with 1 value
"migrations/current.sql": currentSql,
});

const mockedExec: jest.Mock<typeof exec> = exec as any;
mockedExec.mockClear();
mockedExec.mockImplementation((_cmd, _options, callback) =>
callback(null, { stdout: "", stderr: "" }),
);
await current(settingsWithHooks, { forceActions });
const calledActions = mockedExec.mock.calls.map((c) =>
c[0].substring("echo did_".length),
);
expect(calledActions).toEqual(expectedActions);
}
});
12 changes: 10 additions & 2 deletions src/commands/current.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@ import { _migrate } from "./migrate";

interface CurrentArgv extends CommonArgv {
shadow?: boolean;
forceActions?: boolean;
}

export async function current(
settings: Settings,
options: Partial<CurrentArgv> = {},
): Promise<void> {
const { shadow = false } = options;
const { shadow = false, forceActions = false } = options;
const parsedSettings = await parseSettings(settings, shadow);
await _migrate(parsedSettings, shadow);
await _migrate(parsedSettings, shadow, forceActions);

const currentLocation = await getCurrentMigrationLocation(parsedSettings);
if (!currentLocation.exists) {
Expand All @@ -31,6 +32,7 @@ export async function current(
const run = makeCurrentMigrationRunner(parsedSettings, {
once: true,
shadow,
forceActions,
});
return run();
}
Expand All @@ -49,6 +51,12 @@ export const currentCommand: CommandModule<
default: false,
description: "Apply migrations to the shadow DB (for development).",
},
forceActions: {
type: "boolean",
default: false,
description:
"Run beforeAllMigrations, afterAllMigrations, beforeCurrent, and afterCurrent actions even if no migration was necessary.",
},
},
handler: async (argv) => {
const settings = await getSettings({ configFile: argv.config });
Expand Down
12 changes: 8 additions & 4 deletions src/currentRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ export function makeCurrentMigrationRunner(
options: {
once?: boolean;
shadow?: boolean;
forceActions?: boolean;
} = {},
): () => Promise<void> {
const { shadow = false } = options;
const { shadow = false, forceActions = false } = options;
async function run(): Promise<void> {
const currentLocation = await getCurrentMigrationLocation(parsedSettings);
const body = await readCurrentMigration(parsedSettings, currentLocation);
Expand Down Expand Up @@ -74,14 +75,17 @@ export function makeCurrentMigrationRunner(
migrationsAreEquivalent =
currentBodyMinified === previousBodyMinified;

// 4: if different
if (!migrationsAreEquivalent) {
// 3a: Run actions if the migrations are different OR if forced.
if (forceActions || !migrationsAreEquivalent) {
await executeActions(
parsedSettings,
shadow,
parsedSettings.beforeCurrent,
);
}

// 4: if different
if (!migrationsAreEquivalent) {
Comment on lines -77 to +88
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maxkorp This is different to your previous logic - the actions are now ran independently of the database changes themselves being implemented. Is this desired? I think it effectively makes little difference since we're in a transaction so the changes aren't visible anyway...

// 4a: invert previous current; on success delete from graphile_migrate.current; on failure rollback and abort
if (previousBody) {
await reverseMigration(lockingPgClient, previousBody);
Expand Down Expand Up @@ -135,7 +139,7 @@ export function makeCurrentMigrationRunner(
);
const interval = process.hrtime(start);
const duration = interval[0] * 1e3 + interval[1] * 1e-6;
if (!migrationsAreEquivalent) {
if (forceActions || !migrationsAreEquivalent) {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maxkorp unless I messed up all the merges, I think you omitted this change previously?

await executeActions(
parsedSettings,
shadow,
Expand Down
Loading