stepwise-migrations 1.0.22 → 1.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/src/commands.js +136 -0
- package/dist/src/index.js +9 -98
- package/dist/src/utils.js +32 -6
- package/dist/test/index.test.js +2 -2
- package/package.json +1 -1
- package/src/commands.ts +217 -0
- package/src/index.ts +19 -175
- package/src/utils.ts +26 -1
- package/test/index.test.ts +2 -2
- package/test/migrations-invalid/v1_first.sql +0 -2
- package/test/migrations-template/v1_first.sql +0 -2
package/README.md
CHANGED
@@ -3,8 +3,8 @@
|
|
3
3
|
A JavaScript CLI tool for managing Raw SQL migrations in a Postgres database.
|
4
4
|
Loosely based on flyway.
|
5
5
|
|
6
|
-
|
7
|
-

|
7
|
+

|
8
8
|
|
9
9
|
## Table of Contents
|
10
10
|
|
@@ -0,0 +1,136 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
9
|
+
});
|
10
|
+
};
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
12
|
+
exports.getAppliedScriptCommand = exports.auditCommand = exports.undoCommand = exports.dropCommand = exports.validateCommand = exports.statusCommand = exports.infoCommand = exports.migrateCommand = void 0;
|
13
|
+
const db_1 = require("./db");
|
14
|
+
const state_1 = require("./state");
|
15
|
+
const utils_1 = require("./utils");
|
16
|
+
const migrateCommand = (client, argv) => __awaiter(void 0, void 0, void 0, function* () {
|
17
|
+
const { schema, napply, filePath } = (0, utils_1.parseArgs)(argv);
|
18
|
+
const { schemaExists, tableExists } = yield (0, utils_1.checkSchemaAndTable)(client, schema);
|
19
|
+
if (!schemaExists) {
|
20
|
+
yield (0, db_1.dbCreateSchema)(client, schema);
|
21
|
+
}
|
22
|
+
if (!tableExists) {
|
23
|
+
yield (0, db_1.dbCreateEventsTable)(client, schema);
|
24
|
+
}
|
25
|
+
const state = yield (0, state_1.loadState)(client, schema, filePath);
|
26
|
+
(0, utils_1.abortIfErrors)(state);
|
27
|
+
if (state.files.unappliedVersionedFiles.length === 0 &&
|
28
|
+
state.files.unappliedRepeatableFiles.length === 0) {
|
29
|
+
console.log("All migrations are already applied");
|
30
|
+
process.exit(0);
|
31
|
+
}
|
32
|
+
const migrationsToApply = [
|
33
|
+
...state.files.unappliedVersionedFiles,
|
34
|
+
...state.files.unappliedRepeatableFiles,
|
35
|
+
].slice(0, napply);
|
36
|
+
for (const migration of migrationsToApply) {
|
37
|
+
yield (0, db_1.applyMigration)(client, schema, migration);
|
38
|
+
}
|
39
|
+
console.log(`All done! Applied ${migrationsToApply.length} migration${migrationsToApply.length === 1 ? "" : "s"}`);
|
40
|
+
(0, utils_1.printMigrationHistoryAndUnappliedMigrations)(yield (0, state_1.loadState)(client, schema, filePath));
|
41
|
+
});
|
42
|
+
exports.migrateCommand = migrateCommand;
|
43
|
+
const infoCommand = (client, argv) => __awaiter(void 0, void 0, void 0, function* () {
|
44
|
+
const { schema, filePath } = (0, utils_1.parseArgs)(argv);
|
45
|
+
const { schemaExists, tableExists } = yield (0, utils_1.checkSchemaAndTable)(client, schema);
|
46
|
+
if (!schemaExists) {
|
47
|
+
console.log("Schema does not exist");
|
48
|
+
}
|
49
|
+
if (!tableExists) {
|
50
|
+
console.log("Migration table has not been initialised. Run migrate to begin.");
|
51
|
+
}
|
52
|
+
if (schemaExists && tableExists) {
|
53
|
+
(0, utils_1.printMigrationHistoryAndUnappliedMigrations)(yield (0, state_1.loadState)(client, schema, filePath));
|
54
|
+
}
|
55
|
+
});
|
56
|
+
exports.infoCommand = infoCommand;
|
57
|
+
const statusCommand = (client, argv) => __awaiter(void 0, void 0, void 0, function* () {
|
58
|
+
const { schema, filePath } = (0, utils_1.parseArgs)(argv);
|
59
|
+
const { schemaExists, tableExists } = yield (0, utils_1.checkSchemaAndTable)(client, schema);
|
60
|
+
if (!schemaExists) {
|
61
|
+
console.log("Schema does not exist");
|
62
|
+
}
|
63
|
+
if (!tableExists) {
|
64
|
+
console.log("Migration table has not been initialised. Run migrate to begin.");
|
65
|
+
}
|
66
|
+
if (schemaExists && tableExists) {
|
67
|
+
(0, utils_1.printMigrationHistory)(yield (0, state_1.loadState)(client, schema, filePath));
|
68
|
+
}
|
69
|
+
});
|
70
|
+
exports.statusCommand = statusCommand;
|
71
|
+
const validateCommand = (client, argv) => __awaiter(void 0, void 0, void 0, function* () {
|
72
|
+
const { schema } = (0, utils_1.parseArgs)(argv);
|
73
|
+
const { schemaExists, tableExists } = yield (0, utils_1.checkSchemaAndTable)(client, schema);
|
74
|
+
(0, utils_1.exitIfNotInitialized)(schemaExists, tableExists);
|
75
|
+
const state = yield (0, state_1.loadState)(client, schema, argv.path);
|
76
|
+
if (schemaExists && tableExists) {
|
77
|
+
(0, utils_1.abortIfErrors)(state);
|
78
|
+
}
|
79
|
+
console.log("Validation passed");
|
80
|
+
(0, utils_1.printMigrationHistoryAndUnappliedMigrations)(state);
|
81
|
+
});
|
82
|
+
exports.validateCommand = validateCommand;
|
83
|
+
const dropCommand = (client, argv) => __awaiter(void 0, void 0, void 0, function* () {
|
84
|
+
const { schema } = (0, utils_1.parseArgs)(argv);
|
85
|
+
process.stdout.write(`Dropping the tables, schema and migration history table... `);
|
86
|
+
yield (0, db_1.dbDropAll)(client, schema);
|
87
|
+
console.log(`done!`);
|
88
|
+
});
|
89
|
+
exports.dropCommand = dropCommand;
|
90
|
+
const undoCommand = (client, argv) => __awaiter(void 0, void 0, void 0, function* () {
|
91
|
+
const { schema, nundo, filePath } = (0, utils_1.parseArgs)(argv);
|
92
|
+
const state = yield (0, state_1.loadState)(client, schema, filePath);
|
93
|
+
(0, utils_1.abortIfErrors)(state);
|
94
|
+
const reversedAppliedVersionedMigrations = state.current.appliedVersionedMigrations.slice().reverse();
|
95
|
+
const undosToApplyAll = reversedAppliedVersionedMigrations.map((migration) => state.files.undoFiles.find((file) => file.filename === (0, state_1.getUndoFilename)(migration.filename)));
|
96
|
+
const undosToApply = (0, utils_1.sliceFromFirstNull)(undosToApplyAll).slice(0, nundo);
|
97
|
+
if (undosToApply.length < nundo) {
|
98
|
+
console.error(`Error: not enough sequential (from last) undo migrations to apply ${nundo} undos.`);
|
99
|
+
process.exit(1);
|
100
|
+
}
|
101
|
+
for (const { filename, script } of undosToApply) {
|
102
|
+
yield (0, db_1.applyUndoMigration)(client, schema, filename, script);
|
103
|
+
}
|
104
|
+
console.log(`All done! Performed ${undosToApply.length} undo migration${undosToApply.length === 1 ? "" : "s"}`);
|
105
|
+
(0, utils_1.printMigrationHistoryAndUnappliedMigrations)(state);
|
106
|
+
});
|
107
|
+
exports.undoCommand = undoCommand;
|
108
|
+
const auditCommand = (client, argv) => __awaiter(void 0, void 0, void 0, function* () {
|
109
|
+
const { schema } = (0, utils_1.parseArgs)(argv);
|
110
|
+
const { schemaExists, tableExists } = yield (0, utils_1.checkSchemaAndTable)(client, schema);
|
111
|
+
(0, utils_1.exitIfNotInitialized)(schemaExists, tableExists);
|
112
|
+
const state = yield (0, state_1.loadState)(client, schema, argv.path);
|
113
|
+
console.log("Event history:");
|
114
|
+
console.table(state.events.map((row) => ({
|
115
|
+
id: row.id,
|
116
|
+
type: row.type,
|
117
|
+
filename: row.filename,
|
118
|
+
applied_by: row.applied_by,
|
119
|
+
applied_at: row.applied_at,
|
120
|
+
})));
|
121
|
+
});
|
122
|
+
exports.auditCommand = auditCommand;
|
123
|
+
const getAppliedScriptCommand = (client, argv) => __awaiter(void 0, void 0, void 0, function* () {
|
124
|
+
const { schema } = (0, utils_1.parseArgs)(argv);
|
125
|
+
const { schemaExists, tableExists } = yield (0, utils_1.checkSchemaAndTable)(client, schema);
|
126
|
+
(0, utils_1.exitIfNotInitialized)(schemaExists, tableExists);
|
127
|
+
const state = yield (0, state_1.loadState)(client, schema, argv.path);
|
128
|
+
const script = yield (0, db_1.dbGetAppliedScript)(state, argv.filename);
|
129
|
+
if (script) {
|
130
|
+
console.log(script);
|
131
|
+
}
|
132
|
+
else {
|
133
|
+
console.error(`Script for ${argv.filename} not found, use the audit command to check all applied migrations`);
|
134
|
+
}
|
135
|
+
});
|
136
|
+
exports.getAppliedScriptCommand = getAppliedScriptCommand;
|
package/dist/src/index.js
CHANGED
@@ -14,126 +14,37 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
14
14
|
};
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
16
16
|
const yargs_1 = __importDefault(require("yargs"));
|
17
|
+
const commands_1 = require("./commands");
|
17
18
|
const db_1 = require("./db");
|
18
|
-
const state_1 = require("./state");
|
19
19
|
const utils_1 = require("./utils");
|
20
20
|
const main = () => __awaiter(void 0, void 0, void 0, function* () {
|
21
21
|
const argv = (0, yargs_1.default)(process.argv.slice(2)).argv;
|
22
22
|
(0, utils_1.validateArgs)(argv);
|
23
|
-
const schema = argv.schema;
|
24
23
|
const command = argv._[0];
|
25
|
-
const napply = argv.napply || Infinity;
|
26
|
-
const nundo = argv.nundo || 1;
|
27
|
-
const filePath = argv.path;
|
28
24
|
const client = yield (0, db_1.dbConnect)(argv);
|
29
|
-
const schemaExists = yield (0, db_1.dbSchemaExists)(client, schema);
|
30
|
-
const tableExists = yield (0, db_1.dbTableExists)(client, schema);
|
31
25
|
if (command === "migrate") {
|
32
|
-
|
33
|
-
yield (0, db_1.dbCreateSchema)(client, schema);
|
34
|
-
}
|
35
|
-
if (!tableExists) {
|
36
|
-
yield (0, db_1.dbCreateEventsTable)(client, schema);
|
37
|
-
}
|
38
|
-
const state = yield (0, state_1.loadState)(client, schema, argv.path);
|
39
|
-
(0, utils_1.abortIfErrors)(state);
|
40
|
-
if (state.files.unappliedVersionedFiles.length === 0 &&
|
41
|
-
state.files.unappliedRepeatableFiles.length === 0) {
|
42
|
-
console.log("All migrations are already applied");
|
43
|
-
process.exit(0);
|
44
|
-
}
|
45
|
-
const migrationsToApply = [
|
46
|
-
...state.files.unappliedVersionedFiles,
|
47
|
-
...state.files.unappliedRepeatableFiles,
|
48
|
-
].slice(0, napply);
|
49
|
-
for (const migration of migrationsToApply) {
|
50
|
-
yield (0, db_1.applyMigration)(client, schema, migration);
|
51
|
-
}
|
52
|
-
console.log(`All done! Applied ${migrationsToApply.length} migration${migrationsToApply.length === 1 ? "" : "s"}`);
|
53
|
-
(0, utils_1.printMigrationHistoryAndUnappliedMigrations)(yield (0, state_1.loadState)(client, schema, filePath));
|
26
|
+
yield (0, commands_1.migrateCommand)(client, argv);
|
54
27
|
}
|
55
28
|
else if (command === "info") {
|
56
|
-
|
57
|
-
console.log("Schema does not exist");
|
58
|
-
}
|
59
|
-
if (!tableExists) {
|
60
|
-
console.log("Migration table has not been initialised. Run migrate to begin.");
|
61
|
-
}
|
62
|
-
if (schemaExists && tableExists) {
|
63
|
-
(0, utils_1.printMigrationHistoryAndUnappliedMigrations)(yield (0, state_1.loadState)(client, schema, filePath));
|
64
|
-
}
|
29
|
+
yield (0, commands_1.infoCommand)(client, argv);
|
65
30
|
}
|
66
31
|
else if (command === "status") {
|
67
|
-
|
68
|
-
console.log("Schema does not exist");
|
69
|
-
}
|
70
|
-
if (!tableExists) {
|
71
|
-
console.log("Migration table has not been initialised. Run migrate to begin.");
|
72
|
-
}
|
73
|
-
if (schemaExists && tableExists) {
|
74
|
-
(0, utils_1.printMigrationHistoryAndUnappliedMigrations)(yield (0, state_1.loadState)(client, schema, filePath));
|
75
|
-
}
|
32
|
+
yield (0, commands_1.statusCommand)(client, argv);
|
76
33
|
}
|
77
34
|
else if (command === "validate") {
|
78
|
-
(0,
|
79
|
-
const state = yield (0, state_1.loadState)(client, schema, argv.path);
|
80
|
-
if (schemaExists && tableExists) {
|
81
|
-
(0, utils_1.abortIfErrors)(state);
|
82
|
-
}
|
83
|
-
console.log("Validation passed");
|
84
|
-
(0, utils_1.printMigrationHistoryAndUnappliedMigrations)(state);
|
35
|
+
yield (0, commands_1.validateCommand)(client, argv);
|
85
36
|
}
|
86
37
|
else if (command === "drop") {
|
87
|
-
|
88
|
-
yield (0, db_1.dbDropAll)(client, schema);
|
89
|
-
console.log(`done!`);
|
38
|
+
yield (0, commands_1.dropCommand)(client, argv);
|
90
39
|
}
|
91
40
|
else if (command === "undo") {
|
92
|
-
|
93
|
-
(0, utils_1.abortIfErrors)(state);
|
94
|
-
const reversedAppliedVersionedMigrations = state.current.appliedVersionedMigrations.slice().reverse();
|
95
|
-
const undosToApplyAll = reversedAppliedVersionedMigrations.map((migration) => state.files.undoFiles.find((file) => file.filename === (0, state_1.getUndoFilename)(migration.filename)));
|
96
|
-
const undosToApply = (0, utils_1.sliceFromFirstNull)(undosToApplyAll).slice(0, nundo);
|
97
|
-
if (undosToApply.length < nundo) {
|
98
|
-
console.error(`Error: not enough sequential (from last) undo migrations to apply ${nundo} undos.`);
|
99
|
-
process.exit(1);
|
100
|
-
}
|
101
|
-
console.log(undosToApply);
|
102
|
-
for (const { filename, script } of undosToApply) {
|
103
|
-
yield (0, db_1.applyUndoMigration)(client, schema, filename, script);
|
104
|
-
}
|
105
|
-
console.log(`All done! Performed ${undosToApply.length} undo migration${undosToApply.length === 1 ? "" : "s"}`);
|
106
|
-
(0, utils_1.printMigrationHistoryAndUnappliedMigrations)(state);
|
41
|
+
yield (0, commands_1.undoCommand)(client, argv);
|
107
42
|
}
|
108
43
|
else if (command === "audit") {
|
109
|
-
(0,
|
110
|
-
const state = yield (0, state_1.loadState)(client, schema, argv.path);
|
111
|
-
console.log("Event history:");
|
112
|
-
console.table(state.events.map((row) => ({
|
113
|
-
id: row.id,
|
114
|
-
type: row.type,
|
115
|
-
filename: row.filename,
|
116
|
-
applied_by: row.applied_by,
|
117
|
-
applied_at: row.applied_at,
|
118
|
-
})));
|
44
|
+
yield (0, commands_1.auditCommand)(client, argv);
|
119
45
|
}
|
120
46
|
else if (command === "get-applied-script") {
|
121
|
-
|
122
|
-
console.log("Schema does not exist");
|
123
|
-
process.exit(1);
|
124
|
-
}
|
125
|
-
if (!tableExists) {
|
126
|
-
console.log("Migration table has not been initialised. Run migrate to begin.");
|
127
|
-
process.exit(1);
|
128
|
-
}
|
129
|
-
const state = yield (0, state_1.loadState)(client, schema, argv.path);
|
130
|
-
const script = yield (0, db_1.dbGetAppliedScript)(state, argv.filename);
|
131
|
-
if (script) {
|
132
|
-
console.log(script);
|
133
|
-
}
|
134
|
-
else {
|
135
|
-
console.error(`Script for ${argv.filename} not found, use the audit command to check all applied migrations`);
|
136
|
-
}
|
47
|
+
yield (0, commands_1.getAppliedScriptCommand)(client, argv);
|
137
48
|
}
|
138
49
|
else {
|
139
50
|
console.error(`Invalid command: ${command}`);
|
package/dist/src/utils.js
CHANGED
@@ -12,39 +12,57 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
13
13
|
};
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
15
|
-
exports.sliceFromFirstNull = exports.exitIfNotInitialized = exports.abortIfErrors = exports.fileExists = exports.printMigrationHistory = exports.printMigrationHistoryAndUnappliedMigrations = exports.readMigrationFiles = exports.filenameToType = exports.validateArgs = exports.usage = void 0;
|
15
|
+
exports.checkSchemaAndTable = exports.sliceFromFirstNull = exports.exitIfNotInitialized = exports.abortIfErrors = exports.fileExists = exports.printMigrationHistory = exports.printMigrationHistoryAndUnappliedMigrations = exports.readMigrationFiles = exports.filenameToType = exports.validateArgs = exports.parseArgs = exports.usage = void 0;
|
16
16
|
const promises_1 = __importDefault(require("fs/promises"));
|
17
17
|
const git_diff_1 = __importDefault(require("git-diff"));
|
18
18
|
const path_1 = __importDefault(require("path"));
|
19
|
+
const db_1 = require("./db");
|
19
20
|
exports.usage = `
|
20
21
|
Usage: stepwise-migrations [command] [options]
|
21
22
|
|
22
23
|
Commands:
|
23
24
|
migrate
|
24
25
|
Migrate the database to the latest version
|
25
|
-
|
26
|
+
undo
|
26
27
|
Rollback the database to the previous version
|
28
|
+
validate
|
29
|
+
Validate the migration files and the stepwise_migration_events table
|
30
|
+
audit
|
31
|
+
Show the audit history for the migrations in the database
|
27
32
|
info
|
28
33
|
Show information about the current state of the migrations in the database
|
29
34
|
drop
|
30
|
-
Drop
|
35
|
+
Drop all tables, schema and stepwise_migration_events table
|
36
|
+
get-applied-script
|
37
|
+
Get the script for the last applied migration
|
31
38
|
|
32
39
|
Options:
|
33
40
|
--connection <connection> The connection string to use to connect to the database
|
34
41
|
--schema <schema> The schema to use for the migrations
|
35
42
|
--path <path> The path to the migrations directory
|
36
43
|
--ssl true/false Whether to use SSL for the connection (default: false)
|
37
|
-
--napply
|
44
|
+
--napply Number of up migrations to apply (default: all)
|
38
45
|
--nundo Number of undo migrations to apply (default: 1)
|
46
|
+
--filename The filename to get the script for (default: last applied migration)
|
39
47
|
|
40
48
|
Example:
|
41
49
|
npx stepwise-migrations migrate \\
|
42
50
|
--connection=postgresql://postgres:postgres@127.0.0.1:5432/mydatabase \\
|
43
51
|
--schema=myschema \\
|
44
|
-
--path=./
|
52
|
+
--path=./test/migrations-template/
|
45
53
|
`;
|
54
|
+
const parseArgs = (argv) => {
|
55
|
+
var _a;
|
56
|
+
const schema = (_a = argv.schema) !== null && _a !== void 0 ? _a : "public";
|
57
|
+
const command = argv._[0];
|
58
|
+
const napply = argv.napply || Infinity;
|
59
|
+
const nundo = argv.nundo || 1;
|
60
|
+
const filePath = argv.path;
|
61
|
+
return { schema, command, napply, nundo, filePath };
|
62
|
+
};
|
63
|
+
exports.parseArgs = parseArgs;
|
46
64
|
const validateArgs = (argv) => {
|
47
|
-
const required = ["connection", "
|
65
|
+
const required = ["connection", "path", "_"];
|
48
66
|
if (required.some((key) => !(key in argv))) {
|
49
67
|
console.error("Missing required arguments", required.filter((key) => !(key in argv)));
|
50
68
|
console.log(exports.usage);
|
@@ -97,6 +115,7 @@ const readMigrationFiles = (directory, appliedVersionedMigrations) => __awaiter(
|
|
97
115
|
});
|
98
116
|
exports.readMigrationFiles = readMigrationFiles;
|
99
117
|
const printMigrationHistoryAndUnappliedMigrations = (state) => {
|
118
|
+
// console.log(JSON.stringify(state, null, 2));
|
100
119
|
console.log("All applied versioned migrations:");
|
101
120
|
console.table(state.current.appliedVersionedMigrations.map((h) => ({
|
102
121
|
id: h.id,
|
@@ -115,6 +134,7 @@ const printMigrationHistoryAndUnappliedMigrations = (state) => {
|
|
115
134
|
applied_at: h.applied_at,
|
116
135
|
})));
|
117
136
|
}
|
137
|
+
(0, exports.abortIfErrors)(state);
|
118
138
|
console.log("Unapplied versioned migrations:");
|
119
139
|
console.table(state.files.unappliedVersionedFiles.map((h) => ({
|
120
140
|
type: h.type,
|
@@ -185,3 +205,9 @@ const sliceFromFirstNull = (array) => {
|
|
185
205
|
: array.slice(0, indexOfFirstNull);
|
186
206
|
};
|
187
207
|
exports.sliceFromFirstNull = sliceFromFirstNull;
|
208
|
+
const checkSchemaAndTable = (client, schema) => __awaiter(void 0, void 0, void 0, function* () {
|
209
|
+
const schemaExists = yield (0, db_1.dbSchemaExists)(client, schema);
|
210
|
+
const tableExists = yield (0, db_1.dbTableExists)(client, schema);
|
211
|
+
return { schemaExists, tableExists };
|
212
|
+
});
|
213
|
+
exports.checkSchemaAndTable = checkSchemaAndTable;
|
package/dist/test/index.test.js
CHANGED
@@ -100,7 +100,7 @@ const executeCommand = (command, path = "", extraArgs = "") => (0, utils_1.execu
|
|
100
100
|
]);
|
101
101
|
}));
|
102
102
|
}));
|
103
|
-
node_test_1.describe
|
103
|
+
(0, node_test_1.describe)("invalid migrations", () => __awaiter(void 0, void 0, void 0, function* () {
|
104
104
|
(0, node_test_1.beforeEach)(() => __awaiter(void 0, void 0, void 0, function* () {
|
105
105
|
const { output, error, exitCode } = yield executeCommand("drop", "");
|
106
106
|
node_assert_1.default.ok(output.includes("Dropping the tables, schema and migration history table... done!"));
|
@@ -126,7 +126,7 @@ node_test_1.describe.only("invalid migrations", () => __awaiter(void 0, void 0,
|
|
126
126
|
"Versioned migration v1_first.sql has been altered. Cannot migrate in current state.",
|
127
127
|
]);
|
128
128
|
}));
|
129
|
-
node_test_1.it
|
129
|
+
(0, node_test_1.it)("bad creds", () => __awaiter(void 0, void 0, void 0, function* () {
|
130
130
|
(0, utils_1.assertIncludesAll)(yield (0, utils_1.execute)(`npm exec stepwise-migrations info -- \\
|
131
131
|
--connection=postgresql://postgres:badpassword@127.0.0.1:5432/mydb \\
|
132
132
|
--schema=${schema} \\
|
package/package.json
CHANGED
package/src/commands.ts
ADDED
@@ -0,0 +1,217 @@
|
|
1
|
+
import { PoolClient } from "pg";
|
2
|
+
import {
|
3
|
+
applyMigration,
|
4
|
+
applyUndoMigration,
|
5
|
+
dbCreateEventsTable,
|
6
|
+
dbCreateSchema,
|
7
|
+
dbDropAll,
|
8
|
+
dbGetAppliedScript,
|
9
|
+
} from "./db";
|
10
|
+
import { getUndoFilename, loadState } from "./state";
|
11
|
+
import {
|
12
|
+
abortIfErrors,
|
13
|
+
checkSchemaAndTable,
|
14
|
+
exitIfNotInitialized,
|
15
|
+
parseArgs,
|
16
|
+
printMigrationHistory,
|
17
|
+
printMigrationHistoryAndUnappliedMigrations,
|
18
|
+
sliceFromFirstNull,
|
19
|
+
} from "./utils";
|
20
|
+
|
21
|
+
export const migrateCommand = async (client: PoolClient, argv: any) => {
|
22
|
+
const { schema, napply, filePath } = parseArgs(argv);
|
23
|
+
const { schemaExists, tableExists } = await checkSchemaAndTable(
|
24
|
+
client,
|
25
|
+
schema
|
26
|
+
);
|
27
|
+
|
28
|
+
if (!schemaExists) {
|
29
|
+
await dbCreateSchema(client, schema);
|
30
|
+
}
|
31
|
+
if (!tableExists) {
|
32
|
+
await dbCreateEventsTable(client, schema);
|
33
|
+
}
|
34
|
+
|
35
|
+
const state = await loadState(client, schema, filePath);
|
36
|
+
|
37
|
+
abortIfErrors(state);
|
38
|
+
|
39
|
+
if (
|
40
|
+
state.files.unappliedVersionedFiles.length === 0 &&
|
41
|
+
state.files.unappliedRepeatableFiles.length === 0
|
42
|
+
) {
|
43
|
+
console.log("All migrations are already applied");
|
44
|
+
process.exit(0);
|
45
|
+
}
|
46
|
+
|
47
|
+
const migrationsToApply = [
|
48
|
+
...state.files.unappliedVersionedFiles,
|
49
|
+
...state.files.unappliedRepeatableFiles,
|
50
|
+
].slice(0, napply);
|
51
|
+
|
52
|
+
for (const migration of migrationsToApply) {
|
53
|
+
await applyMigration(client, schema, migration);
|
54
|
+
}
|
55
|
+
|
56
|
+
console.log(
|
57
|
+
`All done! Applied ${migrationsToApply.length} migration${
|
58
|
+
migrationsToApply.length === 1 ? "" : "s"
|
59
|
+
}`
|
60
|
+
);
|
61
|
+
|
62
|
+
printMigrationHistoryAndUnappliedMigrations(
|
63
|
+
await loadState(client, schema, filePath)
|
64
|
+
);
|
65
|
+
};
|
66
|
+
|
67
|
+
export const infoCommand = async (client: PoolClient, argv: any) => {
|
68
|
+
const { schema, filePath } = parseArgs(argv);
|
69
|
+
const { schemaExists, tableExists } = await checkSchemaAndTable(
|
70
|
+
client,
|
71
|
+
schema
|
72
|
+
);
|
73
|
+
|
74
|
+
if (!schemaExists) {
|
75
|
+
console.log("Schema does not exist");
|
76
|
+
}
|
77
|
+
|
78
|
+
if (!tableExists) {
|
79
|
+
console.log(
|
80
|
+
"Migration table has not been initialised. Run migrate to begin."
|
81
|
+
);
|
82
|
+
}
|
83
|
+
|
84
|
+
if (schemaExists && tableExists) {
|
85
|
+
printMigrationHistoryAndUnappliedMigrations(
|
86
|
+
await loadState(client, schema, filePath)
|
87
|
+
);
|
88
|
+
}
|
89
|
+
};
|
90
|
+
|
91
|
+
export const statusCommand = async (client: PoolClient, argv: any) => {
|
92
|
+
const { schema, filePath } = parseArgs(argv);
|
93
|
+
const { schemaExists, tableExists } = await checkSchemaAndTable(
|
94
|
+
client,
|
95
|
+
schema
|
96
|
+
);
|
97
|
+
if (!schemaExists) {
|
98
|
+
console.log("Schema does not exist");
|
99
|
+
}
|
100
|
+
|
101
|
+
if (!tableExists) {
|
102
|
+
console.log(
|
103
|
+
"Migration table has not been initialised. Run migrate to begin."
|
104
|
+
);
|
105
|
+
}
|
106
|
+
|
107
|
+
if (schemaExists && tableExists) {
|
108
|
+
printMigrationHistory(await loadState(client, schema, filePath));
|
109
|
+
}
|
110
|
+
};
|
111
|
+
|
112
|
+
export const validateCommand = async (client: PoolClient, argv: any) => {
|
113
|
+
const { schema } = parseArgs(argv);
|
114
|
+
const { schemaExists, tableExists } = await checkSchemaAndTable(
|
115
|
+
client,
|
116
|
+
schema
|
117
|
+
);
|
118
|
+
exitIfNotInitialized(schemaExists, tableExists);
|
119
|
+
|
120
|
+
const state = await loadState(client, schema, argv.path);
|
121
|
+
if (schemaExists && tableExists) {
|
122
|
+
abortIfErrors(state);
|
123
|
+
}
|
124
|
+
console.log("Validation passed");
|
125
|
+
|
126
|
+
printMigrationHistoryAndUnappliedMigrations(state);
|
127
|
+
};
|
128
|
+
|
129
|
+
export const dropCommand = async (client: PoolClient, argv: any) => {
|
130
|
+
const { schema } = parseArgs(argv);
|
131
|
+
process.stdout.write(
|
132
|
+
`Dropping the tables, schema and migration history table... `
|
133
|
+
);
|
134
|
+
await dbDropAll(client, schema);
|
135
|
+
console.log(`done!`);
|
136
|
+
};
|
137
|
+
|
138
|
+
export const undoCommand = async (client: PoolClient, argv: any) => {
|
139
|
+
const { schema, nundo, filePath } = parseArgs(argv);
|
140
|
+
const state = await loadState(client, schema, filePath);
|
141
|
+
|
142
|
+
abortIfErrors(state);
|
143
|
+
|
144
|
+
const reversedAppliedVersionedMigrations =
|
145
|
+
state.current.appliedVersionedMigrations.slice().reverse();
|
146
|
+
|
147
|
+
const undosToApplyAll = reversedAppliedVersionedMigrations.map((migration) =>
|
148
|
+
state.files.undoFiles.find(
|
149
|
+
(file) => file.filename === getUndoFilename(migration.filename)
|
150
|
+
)
|
151
|
+
);
|
152
|
+
|
153
|
+
const undosToApply = sliceFromFirstNull(undosToApplyAll).slice(0, nundo);
|
154
|
+
|
155
|
+
if (undosToApply.length < nundo) {
|
156
|
+
console.error(
|
157
|
+
`Error: not enough sequential (from last) undo migrations to apply ${nundo} undos.`
|
158
|
+
);
|
159
|
+
process.exit(1);
|
160
|
+
}
|
161
|
+
|
162
|
+
for (const { filename, script } of undosToApply) {
|
163
|
+
await applyUndoMigration(client, schema, filename, script);
|
164
|
+
}
|
165
|
+
console.log(
|
166
|
+
`All done! Performed ${undosToApply.length} undo migration${
|
167
|
+
undosToApply.length === 1 ? "" : "s"
|
168
|
+
}`
|
169
|
+
);
|
170
|
+
|
171
|
+
printMigrationHistoryAndUnappliedMigrations(state);
|
172
|
+
};
|
173
|
+
|
174
|
+
export const auditCommand = async (client: PoolClient, argv: any) => {
|
175
|
+
const { schema } = parseArgs(argv);
|
176
|
+
const { schemaExists, tableExists } = await checkSchemaAndTable(
|
177
|
+
client,
|
178
|
+
schema
|
179
|
+
);
|
180
|
+
|
181
|
+
exitIfNotInitialized(schemaExists, tableExists);
|
182
|
+
|
183
|
+
const state = await loadState(client, schema, argv.path);
|
184
|
+
console.log("Event history:");
|
185
|
+
console.table(
|
186
|
+
state.events.map((row) => ({
|
187
|
+
id: row.id,
|
188
|
+
type: row.type,
|
189
|
+
filename: row.filename,
|
190
|
+
applied_by: row.applied_by,
|
191
|
+
applied_at: row.applied_at,
|
192
|
+
}))
|
193
|
+
);
|
194
|
+
};
|
195
|
+
|
196
|
+
export const getAppliedScriptCommand = async (
|
197
|
+
client: PoolClient,
|
198
|
+
argv: any
|
199
|
+
) => {
|
200
|
+
const { schema } = parseArgs(argv);
|
201
|
+
const { schemaExists, tableExists } = await checkSchemaAndTable(
|
202
|
+
client,
|
203
|
+
schema
|
204
|
+
);
|
205
|
+
|
206
|
+
exitIfNotInitialized(schemaExists, tableExists);
|
207
|
+
|
208
|
+
const state = await loadState(client, schema, argv.path);
|
209
|
+
const script = await dbGetAppliedScript(state, argv.filename);
|
210
|
+
if (script) {
|
211
|
+
console.log(script);
|
212
|
+
} else {
|
213
|
+
console.error(
|
214
|
+
`Script for ${argv.filename} not found, use the audit command to check all applied migrations`
|
215
|
+
);
|
216
|
+
}
|
217
|
+
};
|
package/src/index.ts
CHANGED
@@ -2,197 +2,41 @@
|
|
2
2
|
|
3
3
|
import yargs from "yargs";
|
4
4
|
import {
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
} from "./db";
|
15
|
-
import {
|
16
|
-
import {
|
17
|
-
abortIfErrors,
|
18
|
-
exitIfNotInitialized,
|
19
|
-
printMigrationHistoryAndUnappliedMigrations,
|
20
|
-
sliceFromFirstNull,
|
21
|
-
usage,
|
22
|
-
validateArgs,
|
23
|
-
} from "./utils";
|
5
|
+
auditCommand,
|
6
|
+
dropCommand,
|
7
|
+
getAppliedScriptCommand,
|
8
|
+
infoCommand,
|
9
|
+
migrateCommand,
|
10
|
+
statusCommand,
|
11
|
+
undoCommand,
|
12
|
+
validateCommand,
|
13
|
+
} from "./commands";
|
14
|
+
import { dbConnect } from "./db";
|
15
|
+
import { usage, validateArgs } from "./utils";
|
24
16
|
|
25
17
|
const main = async () => {
|
26
18
|
const argv: any = yargs(process.argv.slice(2)).argv;
|
27
|
-
|
28
19
|
validateArgs(argv);
|
29
20
|
|
30
|
-
const schema = argv.schema;
|
31
21
|
const command = argv._[0];
|
32
|
-
const napply = argv.napply || Infinity;
|
33
|
-
const nundo = argv.nundo || 1;
|
34
|
-
const filePath = argv.path;
|
35
|
-
|
36
22
|
const client = await dbConnect(argv);
|
37
|
-
const schemaExists = await dbSchemaExists(client, schema);
|
38
|
-
const tableExists = await dbTableExists(client, schema);
|
39
23
|
|
40
24
|
if (command === "migrate") {
|
41
|
-
|
42
|
-
await dbCreateSchema(client, schema);
|
43
|
-
}
|
44
|
-
if (!tableExists) {
|
45
|
-
await dbCreateEventsTable(client, schema);
|
46
|
-
}
|
47
|
-
|
48
|
-
const state = await loadState(client, schema, argv.path);
|
49
|
-
|
50
|
-
abortIfErrors(state);
|
51
|
-
|
52
|
-
if (
|
53
|
-
state.files.unappliedVersionedFiles.length === 0 &&
|
54
|
-
state.files.unappliedRepeatableFiles.length === 0
|
55
|
-
) {
|
56
|
-
console.log("All migrations are already applied");
|
57
|
-
process.exit(0);
|
58
|
-
}
|
59
|
-
|
60
|
-
const migrationsToApply = [
|
61
|
-
...state.files.unappliedVersionedFiles,
|
62
|
-
...state.files.unappliedRepeatableFiles,
|
63
|
-
].slice(0, napply);
|
64
|
-
|
65
|
-
for (const migration of migrationsToApply) {
|
66
|
-
await applyMigration(client, schema, migration);
|
67
|
-
}
|
68
|
-
|
69
|
-
console.log(
|
70
|
-
`All done! Applied ${migrationsToApply.length} migration${
|
71
|
-
migrationsToApply.length === 1 ? "" : "s"
|
72
|
-
}`
|
73
|
-
);
|
74
|
-
|
75
|
-
printMigrationHistoryAndUnappliedMigrations(
|
76
|
-
await loadState(client, schema, filePath)
|
77
|
-
);
|
25
|
+
await migrateCommand(client, argv);
|
78
26
|
} else if (command === "info") {
|
79
|
-
|
80
|
-
console.log("Schema does not exist");
|
81
|
-
}
|
82
|
-
|
83
|
-
if (!tableExists) {
|
84
|
-
console.log(
|
85
|
-
"Migration table has not been initialised. Run migrate to begin."
|
86
|
-
);
|
87
|
-
}
|
88
|
-
|
89
|
-
if (schemaExists && tableExists) {
|
90
|
-
printMigrationHistoryAndUnappliedMigrations(
|
91
|
-
await loadState(client, schema, filePath)
|
92
|
-
);
|
93
|
-
}
|
27
|
+
await infoCommand(client, argv);
|
94
28
|
} else if (command === "status") {
|
95
|
-
|
96
|
-
console.log("Schema does not exist");
|
97
|
-
}
|
98
|
-
|
99
|
-
if (!tableExists) {
|
100
|
-
console.log(
|
101
|
-
"Migration table has not been initialised. Run migrate to begin."
|
102
|
-
);
|
103
|
-
}
|
104
|
-
|
105
|
-
if (schemaExists && tableExists) {
|
106
|
-
printMigrationHistoryAndUnappliedMigrations(
|
107
|
-
await loadState(client, schema, filePath)
|
108
|
-
);
|
109
|
-
}
|
29
|
+
await statusCommand(client, argv);
|
110
30
|
} else if (command === "validate") {
|
111
|
-
|
112
|
-
|
113
|
-
const state = await loadState(client, schema, argv.path);
|
114
|
-
if (schemaExists && tableExists) {
|
115
|
-
abortIfErrors(state);
|
116
|
-
}
|
117
|
-
console.log("Validation passed");
|
118
|
-
|
119
|
-
printMigrationHistoryAndUnappliedMigrations(state);
|
31
|
+
await validateCommand(client, argv);
|
120
32
|
} else if (command === "drop") {
|
121
|
-
|
122
|
-
`Dropping the tables, schema and migration history table... `
|
123
|
-
);
|
124
|
-
await dbDropAll(client, schema);
|
125
|
-
console.log(`done!`);
|
33
|
+
await dropCommand(client, argv);
|
126
34
|
} else if (command === "undo") {
|
127
|
-
|
128
|
-
|
129
|
-
abortIfErrors(state);
|
130
|
-
|
131
|
-
const reversedAppliedVersionedMigrations =
|
132
|
-
state.current.appliedVersionedMigrations.slice().reverse();
|
133
|
-
|
134
|
-
const undosToApplyAll = reversedAppliedVersionedMigrations.map(
|
135
|
-
(migration) =>
|
136
|
-
state.files.undoFiles.find(
|
137
|
-
(file) => file.filename === getUndoFilename(migration.filename)
|
138
|
-
)
|
139
|
-
);
|
140
|
-
|
141
|
-
const undosToApply = sliceFromFirstNull(undosToApplyAll).slice(0, nundo);
|
142
|
-
|
143
|
-
if (undosToApply.length < nundo) {
|
144
|
-
console.error(
|
145
|
-
`Error: not enough sequential (from last) undo migrations to apply ${nundo} undos.`
|
146
|
-
);
|
147
|
-
process.exit(1);
|
148
|
-
}
|
149
|
-
|
150
|
-
for (const { filename, script } of undosToApply) {
|
151
|
-
await applyUndoMigration(client, schema, filename, script);
|
152
|
-
}
|
153
|
-
console.log(
|
154
|
-
`All done! Performed ${undosToApply.length} undo migration${
|
155
|
-
undosToApply.length === 1 ? "" : "s"
|
156
|
-
}`
|
157
|
-
);
|
158
|
-
|
159
|
-
printMigrationHistoryAndUnappliedMigrations(state);
|
35
|
+
await undoCommand(client, argv);
|
160
36
|
} else if (command === "audit") {
|
161
|
-
|
162
|
-
|
163
|
-
const state = await loadState(client, schema, argv.path);
|
164
|
-
console.log("Event history:");
|
165
|
-
console.table(
|
166
|
-
state.events.map((row) => ({
|
167
|
-
id: row.id,
|
168
|
-
type: row.type,
|
169
|
-
filename: row.filename,
|
170
|
-
applied_by: row.applied_by,
|
171
|
-
applied_at: row.applied_at,
|
172
|
-
}))
|
173
|
-
);
|
37
|
+
await auditCommand(client, argv);
|
174
38
|
} else if (command === "get-applied-script") {
|
175
|
-
|
176
|
-
console.log("Schema does not exist");
|
177
|
-
process.exit(1);
|
178
|
-
}
|
179
|
-
|
180
|
-
if (!tableExists) {
|
181
|
-
console.log(
|
182
|
-
"Migration table has not been initialised. Run migrate to begin."
|
183
|
-
);
|
184
|
-
process.exit(1);
|
185
|
-
}
|
186
|
-
|
187
|
-
const state = await loadState(client, schema, argv.path);
|
188
|
-
const script = await dbGetAppliedScript(state, argv.filename);
|
189
|
-
if (script) {
|
190
|
-
console.log(script);
|
191
|
-
} else {
|
192
|
-
console.error(
|
193
|
-
`Script for ${argv.filename} not found, use the audit command to check all applied migrations`
|
194
|
-
);
|
195
|
-
}
|
39
|
+
await getAppliedScriptCommand(client, argv);
|
196
40
|
} else {
|
197
41
|
console.error(`Invalid command: ${command}`);
|
198
42
|
console.log(usage);
|
package/src/utils.ts
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
import fs from "fs/promises";
|
2
2
|
import gitDiff from "git-diff";
|
3
3
|
import path from "path";
|
4
|
+
import { PoolClient } from "pg";
|
5
|
+
import { dbSchemaExists, dbTableExists } from "./db";
|
4
6
|
import {
|
5
7
|
AppliedMigration,
|
6
8
|
MigrationFile,
|
@@ -43,8 +45,18 @@ Example:
|
|
43
45
|
--path=./test/migrations-template/
|
44
46
|
`;
|
45
47
|
|
48
|
+
export const parseArgs = (argv: any) => {
|
49
|
+
const schema = argv.schema ?? "public";
|
50
|
+
const command = argv._[0];
|
51
|
+
const napply = argv.napply || Infinity;
|
52
|
+
const nundo = argv.nundo || 1;
|
53
|
+
const filePath = argv.path;
|
54
|
+
|
55
|
+
return { schema, command, napply, nundo, filePath };
|
56
|
+
};
|
57
|
+
|
46
58
|
export const validateArgs = (argv: any) => {
|
47
|
-
const required = ["connection", "
|
59
|
+
const required = ["connection", "path", "_"];
|
48
60
|
if (required.some((key) => !(key in argv))) {
|
49
61
|
console.error(
|
50
62
|
"Missing required arguments",
|
@@ -116,6 +128,7 @@ export const readMigrationFiles = async (
|
|
116
128
|
export const printMigrationHistoryAndUnappliedMigrations = (
|
117
129
|
state: MigrationState
|
118
130
|
) => {
|
131
|
+
// console.log(JSON.stringify(state, null, 2));
|
119
132
|
console.log("All applied versioned migrations:");
|
120
133
|
console.table(
|
121
134
|
state.current.appliedVersionedMigrations.map((h) => ({
|
@@ -138,6 +151,9 @@ export const printMigrationHistoryAndUnappliedMigrations = (
|
|
138
151
|
}))
|
139
152
|
);
|
140
153
|
}
|
154
|
+
|
155
|
+
abortIfErrors(state);
|
156
|
+
|
141
157
|
console.log("Unapplied versioned migrations:");
|
142
158
|
console.table(
|
143
159
|
state.files.unappliedVersionedFiles.map((h) => ({
|
@@ -222,3 +238,12 @@ export const sliceFromFirstNull = <T>(array: (T | undefined)[]): T[] => {
|
|
222
238
|
? (array as T[])
|
223
239
|
: (array.slice(0, indexOfFirstNull) as T[]);
|
224
240
|
};
|
241
|
+
|
242
|
+
export const checkSchemaAndTable = async (
|
243
|
+
client: PoolClient,
|
244
|
+
schema: string
|
245
|
+
) => {
|
246
|
+
const schemaExists = await dbSchemaExists(client, schema);
|
247
|
+
const tableExists = await dbTableExists(client, schema);
|
248
|
+
return { schemaExists, tableExists };
|
249
|
+
};
|
package/test/index.test.ts
CHANGED
@@ -120,7 +120,7 @@ describe("valid migrations", async () => {
|
|
120
120
|
});
|
121
121
|
});
|
122
122
|
|
123
|
-
describe
|
123
|
+
describe("invalid migrations", async () => {
|
124
124
|
beforeEach(async () => {
|
125
125
|
const { output, error, exitCode } = await executeCommand("drop", "");
|
126
126
|
assert.ok(
|
@@ -164,7 +164,7 @@ describe.only("invalid migrations", async () => {
|
|
164
164
|
]);
|
165
165
|
});
|
166
166
|
|
167
|
-
it
|
167
|
+
it("bad creds", async () => {
|
168
168
|
assertIncludesAll(
|
169
169
|
await execute(`npm exec stepwise-migrations info -- \\
|
170
170
|
--connection=postgresql://postgres:badpassword@127.0.0.1:5432/mydb \\
|