stepwise-migrations 1.0.14 → 1.0.16
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 +20 -20
- package/dist/{db.js → src/db.js} +73 -28
- package/dist/src/index.js +146 -0
- package/dist/src/state.js +123 -0
- package/dist/src/types.js +13 -0
- package/dist/src/utils.js +184 -0
- package/dist/src/validate.js +1 -0
- package/dist/test/index.test.js +129 -0
- package/dist/test/utils.js +51 -0
- package/docker-compose.yml +21 -0
- package/package.json +12 -6
- package/src/db.ts +92 -37
- package/src/index.ts +115 -80
- package/src/state.ts +166 -0
- package/src/types.ts +49 -4
- package/src/utils.ts +122 -66
- package/test/index.test.ts +166 -0
- package/test/migrations-invalid/v0_get_number.repeatable.sql +6 -0
- package/test/migrations-invalid/v1_first.sql +1 -0
- package/test/migrations-invalid/v2_second.sql +4 -0
- package/test/migrations-invalid/v2_second.undo.sql +1 -0
- package/test/migrations-invalid/v3_third.sql +4 -0
- package/test/migrations-invalid/v3_third.undo.sql +1 -0
- package/test/migrations-template/v0_get_number.repeatable.sql +6 -0
- package/test/migrations-template/v1_first.sql +4 -0
- package/test/migrations-template/v2_second.sql +4 -0
- package/test/migrations-template/v2_second.undo.sql +1 -0
- package/test/migrations-template/v3_third.sql +4 -0
- package/test/migrations-template/v3_third.undo.sql +1 -0
- package/test/migrations-valid/v0_get_number.repeatable.sql +8 -0
- package/test/migrations-valid/v1_first.sql +4 -0
- package/test/migrations-valid/v2_second.sql +4 -0
- package/test/migrations-valid/v2_second.undo.sql +1 -0
- package/test/migrations-valid/v3_third.sql +4 -0
- package/test/migrations-valid/v3_third.undo.sql +1 -0
- package/test/utils.ts +69 -0
- package/tsconfig.json +1 -1
- package/dist/index.js +0 -115
- package/dist/migrate.js +0 -102
- package/dist/types.js +0 -2
- package/dist/utils.js +0 -132
- package/src/migrate.ts +0 -143
package/README.md
CHANGED
@@ -10,8 +10,8 @@ Loosely based on flyway.
|
|
10
10
|
Up migrations are first sorted in ascending order based on filename.
|
11
11
|
No subdirectories are read below the migration directory.
|
12
12
|
|
13
|
-
Name the "up" migration files as `.sql` and the "down" migration files with the same name but suffixed with `.
|
14
|
-
e.g. `v1_users.sql` and `v1_users.
|
13
|
+
Name the "up" migration files as `.sql` and the "down" migration files with the same name but suffixed with `.undo.sql`.
|
14
|
+
e.g. `v1_users.sql` and `v1_users.undo.sql`.
|
15
15
|
Down migrations are optional.
|
16
16
|
|
17
17
|
## Usage
|
@@ -22,17 +22,17 @@ Usage: stepwise-migrations [command] [options]
|
|
22
22
|
Commands:
|
23
23
|
migrate
|
24
24
|
Migrate the database to the latest version
|
25
|
-
|
25
|
+
undo
|
26
26
|
Rollback the database to the previous version
|
27
27
|
validate
|
28
|
-
Validate the migration files and the
|
28
|
+
Validate the migration files and the stepwise_migration_events table
|
29
29
|
audit
|
30
30
|
Show the audit history for the migrations in the database
|
31
31
|
info
|
32
32
|
Show information about the current state of the migrations in the database
|
33
33
|
drop
|
34
|
-
Drop all tables, schema and
|
35
|
-
get-script
|
34
|
+
Drop all tables, schema and stepwise_migration_events table
|
35
|
+
get-applied-script
|
36
36
|
Get the script for the last applied migration
|
37
37
|
|
38
38
|
Options:
|
@@ -40,8 +40,8 @@ Options:
|
|
40
40
|
--schema <schema> The schema to use for the migrations
|
41
41
|
--path <path> The path to the migrations directory
|
42
42
|
--ssl true/false Whether to use SSL for the connection (default: false)
|
43
|
-
--
|
44
|
-
--
|
43
|
+
--napply Number of up migrations to apply (default: all)
|
44
|
+
--nundo Number of down migrations to apply (default: 1)
|
45
45
|
--filename The filename to get the script for (default: last applied migration)
|
46
46
|
|
47
47
|
Example:
|
@@ -70,11 +70,11 @@ npx stepwise-migrations migrate \
|
|
70
70
|
|
71
71
|
```text
|
72
72
|
Creating schema myschema... done!
|
73
|
-
Creating
|
73
|
+
Creating stepwise_migration_events table... done!
|
74
74
|
Applying migration v1_connect_session_table.sql... done!
|
75
75
|
Applying migration v2_auth.sql... done!
|
76
76
|
All done! Applied 2 migrations
|
77
|
-
Migration
|
77
|
+
Migration state:
|
78
78
|
┌─────────┬────┬────────────────────────────────┬────────────┬──────────────────────────────┐
|
79
79
|
│ (index) │ id │ name │ applied_by │ applied_at │
|
80
80
|
├─────────┼────┼────────────────────────────────┼────────────┼──────────────────────────────┤
|
@@ -89,10 +89,10 @@ Unapplied migrations:
|
|
89
89
|
|
90
90
|
</details>
|
91
91
|
|
92
|
-
###
|
92
|
+
### Undo
|
93
93
|
|
94
94
|
Runs a single down migration for the last applied migration.
|
95
|
-
Can run multiple down migrations if the `--
|
95
|
+
Can run multiple down migrations if the `--nundo` option is provided.
|
96
96
|
|
97
97
|
Command:
|
98
98
|
|
@@ -108,9 +108,9 @@ npx stepwise-migrations down \
|
|
108
108
|
<summary>Example output</summary>
|
109
109
|
|
110
110
|
```text
|
111
|
-
Applying down migration v2_auth.
|
111
|
+
Applying down migration v2_auth.undo.sql... done!
|
112
112
|
All done! Applied 1 down migration
|
113
|
-
Migration
|
113
|
+
Migration state:
|
114
114
|
┌─────────┬────┬────────────────────────────────┬────────────┬──────────────────────────────┐
|
115
115
|
│ (index) │ id │ name │ applied_by │ applied_at │
|
116
116
|
├─────────┼────┼────────────────────────────────┼────────────┼──────────────────────────────┤
|
@@ -128,7 +128,7 @@ Unapplied migrations:
|
|
128
128
|
|
129
129
|
### Validate
|
130
130
|
|
131
|
-
Validates the migration files and the
|
131
|
+
Validates the migration files and the stepwise_migration_events table.
|
132
132
|
|
133
133
|
```bash
|
134
134
|
npx stepwise-migrations validate \
|
@@ -143,7 +143,7 @@ npx stepwise-migrations validate \
|
|
143
143
|
|
144
144
|
```text
|
145
145
|
Validation passed
|
146
|
-
Migration
|
146
|
+
Migration state:
|
147
147
|
┌─────────┬────┬────────────────────────────────┬────────────┬──────────────────────────────┐
|
148
148
|
│ (index) │ id │ name │ applied_by │ applied_at │
|
149
149
|
├─────────┼────┼────────────────────────────────┼────────────┼──────────────────────────────┤
|
@@ -199,7 +199,7 @@ Audit history:
|
|
199
199
|
├─────────┼────┼────────┼────────────────────────────────┼────────────┼──────────────────────────────┤
|
200
200
|
│ 0 │ 1 │ 'up' │ 'v1_connect_session_table.sql' │ 'postgres' │ '2024-11-24 05:40:41.211617' │
|
201
201
|
│ 1 │ 2 │ 'up' │ 'v2_auth.sql' │ 'postgres' │ '2024-11-24 05:40:41.214732' │
|
202
|
-
│ 2 │ 3 │ 'down' │ 'v2_auth.
|
202
|
+
│ 2 │ 3 │ 'down' │ 'v2_auth.undo.sql' │ 'postgres' │ '2024-11-24 05:41:34.541462' │
|
203
203
|
└─────────┴────┴────────┴────────────────────────────────┴────────────┴──────────────────────────────┘
|
204
204
|
```
|
205
205
|
|
@@ -223,7 +223,7 @@ npx stepwise-migrations info \
|
|
223
223
|
<summary>Example output</summary>
|
224
224
|
|
225
225
|
```text
|
226
|
-
Migration
|
226
|
+
Migration state:
|
227
227
|
┌─────────┬────┬────────────────────────────────┬────────────┬──────────────────────────────┐
|
228
228
|
│ (index) │ id │ name │ applied_by │ applied_at │
|
229
229
|
├─────────┼────┼────────────────────────────────┼────────────┼──────────────────────────────┤
|
@@ -265,7 +265,7 @@ CREATE TABLE "users" (
|
|
265
265
|
|
266
266
|
### Drop
|
267
267
|
|
268
|
-
Drops the tables, schema and
|
268
|
+
Drops the tables, schema and stepwise_migration_events table.
|
269
269
|
|
270
270
|
Command:
|
271
271
|
|
@@ -280,7 +280,7 @@ npx stepwise-migrations drop \
|
|
280
280
|
<summary>Example output</summary>
|
281
281
|
|
282
282
|
```text
|
283
|
-
Dropping the tables, schema and migration
|
283
|
+
Dropping the tables, schema and migration stepwise_migration_events table... done!
|
284
284
|
```
|
285
285
|
|
286
286
|
</details>
|
package/dist/{db.js → src/db.js}
RENAMED
@@ -42,8 +42,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
42
42
|
});
|
43
43
|
};
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
45
|
-
exports.
|
45
|
+
exports.applyUndoMigration = exports.applyMigration = exports.dbGetAppliedScript = exports.dbCreateEventsTable = exports.dbEventHistory = exports.dbCreateSchema = exports.dbDropAll = exports.dbTableExists = exports.dbSchemaExists = exports.dbConnect = void 0;
|
46
46
|
const pg_1 = __importStar(require("pg"));
|
47
|
+
const types_1 = require("./types");
|
47
48
|
pg_1.default.types.setTypeParser(1114, function (stringValue) {
|
48
49
|
return stringValue; //1114 for time without timezone type
|
49
50
|
});
|
@@ -67,46 +68,44 @@ const dbConnect = (argv) => __awaiter(void 0, void 0, void 0, function* () {
|
|
67
68
|
return client;
|
68
69
|
});
|
69
70
|
exports.dbConnect = dbConnect;
|
70
|
-
const
|
71
|
+
const dbSchemaExists = (client, schema) => __awaiter(void 0, void 0, void 0, function* () {
|
71
72
|
const result = yield client.query(`SELECT EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = '${schema}')`);
|
72
73
|
return result.rows[0].exists;
|
73
74
|
});
|
74
|
-
exports.
|
75
|
+
exports.dbSchemaExists = dbSchemaExists;
|
75
76
|
const dbTableExists = (client, schema) => __awaiter(void 0, void 0, void 0, function* () {
|
76
|
-
const tableExistsResult = yield client.query(`SELECT EXISTS (SELECT 1 FROM pg_tables WHERE tablename = '
|
77
|
+
const tableExistsResult = yield client.query(`SELECT EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'stepwise_migration_events' and schemaname = '${schema}')`);
|
77
78
|
return tableExistsResult.rows[0].exists;
|
78
79
|
});
|
79
80
|
exports.dbTableExists = dbTableExists;
|
80
|
-
const
|
81
|
-
|
82
|
-
return migrationsQuery.rows;
|
81
|
+
const dbDropAll = (client, schema) => __awaiter(void 0, void 0, void 0, function* () {
|
82
|
+
yield client.query(`DROP SCHEMA IF EXISTS ${schema} CASCADE`);
|
83
83
|
});
|
84
|
-
exports.
|
84
|
+
exports.dbDropAll = dbDropAll;
|
85
85
|
const dbCreateSchema = (client, schema) => __awaiter(void 0, void 0, void 0, function* () {
|
86
86
|
process.stdout.write(`Creating schema ${schema}... `);
|
87
87
|
yield client.query(`CREATE SCHEMA IF NOT EXISTS ${schema}`);
|
88
88
|
console.log(`done!`);
|
89
89
|
});
|
90
90
|
exports.dbCreateSchema = dbCreateSchema;
|
91
|
-
const
|
92
|
-
|
93
|
-
|
91
|
+
const dbEventHistory = (client, schema) => __awaiter(void 0, void 0, void 0, function* () {
|
92
|
+
try {
|
93
|
+
const eventQuery = yield client.query(`SELECT * FROM ${schema}.stepwise_migration_events`);
|
94
|
+
return eventQuery.rows.map((row) => types_1.EventRow.parse(row));
|
95
|
+
}
|
96
|
+
catch (error) {
|
97
|
+
console.error("Error fetching event history", error);
|
98
|
+
process.exit(1);
|
99
|
+
}
|
94
100
|
});
|
95
|
-
exports.
|
96
|
-
const
|
97
|
-
process.stdout.write(`Creating
|
101
|
+
exports.dbEventHistory = dbEventHistory;
|
102
|
+
const dbCreateEventsTable = (client, schema) => __awaiter(void 0, void 0, void 0, function* () {
|
103
|
+
process.stdout.write(`Creating stepwise_migration_events table... `);
|
98
104
|
yield client.query(`
|
99
|
-
CREATE TABLE IF NOT EXISTS ${schema}.
|
100
|
-
id SERIAL PRIMARY KEY,
|
101
|
-
name TEXT UNIQUE NOT NULL,
|
102
|
-
script TEXT NOT NULL,
|
103
|
-
applied_by TEXT NOT NULL DEFAULT current_user,
|
104
|
-
applied_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
105
|
-
);
|
106
|
-
CREATE TABLE IF NOT EXISTS ${schema}.stepwise_audit (
|
105
|
+
CREATE TABLE IF NOT EXISTS ${schema}.stepwise_migration_events (
|
107
106
|
id SERIAL PRIMARY KEY,
|
108
107
|
type TEXT NOT NULL,
|
109
|
-
|
108
|
+
filename TEXT NOT NULL,
|
110
109
|
script TEXT NOT NULL,
|
111
110
|
applied_by TEXT NOT NULL DEFAULT current_user,
|
112
111
|
applied_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
@@ -114,9 +113,55 @@ CREATE TABLE IF NOT EXISTS ${schema}.stepwise_audit (
|
|
114
113
|
`);
|
115
114
|
console.log(`done!`);
|
116
115
|
});
|
117
|
-
exports.
|
118
|
-
const
|
119
|
-
|
120
|
-
return
|
116
|
+
exports.dbCreateEventsTable = dbCreateEventsTable;
|
117
|
+
const dbGetAppliedScript = (state, filename) => __awaiter(void 0, void 0, void 0, function* () {
|
118
|
+
var _a;
|
119
|
+
return (_a = state.current.appliedVersionedMigrations
|
120
|
+
.concat(state.current.appliedRepeatableMigrations)
|
121
|
+
.find((file) => file.filename === filename)) === null || _a === void 0 ? void 0 : _a.script;
|
122
|
+
});
|
123
|
+
exports.dbGetAppliedScript = dbGetAppliedScript;
|
124
|
+
const applyMigration = (client, schema, migration) => __awaiter(void 0, void 0, void 0, function* () {
|
125
|
+
try {
|
126
|
+
process.stdout.write(`Applying ${migration.type} migration ${migration.filename}... `);
|
127
|
+
yield client.query("BEGIN");
|
128
|
+
yield client.query(`SET search_path TO ${schema};
|
129
|
+
${migration.script.toString()}`);
|
130
|
+
yield client.query(`INSERT INTO ${schema}.stepwise_migration_events (type, filename, script) VALUES ($1, $2, $3)`, [migration.type, migration.filename, migration.script]);
|
131
|
+
yield client.query("COMMIT");
|
132
|
+
console.log(`done!`);
|
133
|
+
}
|
134
|
+
catch (error) {
|
135
|
+
try {
|
136
|
+
yield client.query("ROLLBACK");
|
137
|
+
}
|
138
|
+
catch (error) {
|
139
|
+
console.error("Error rolling back transaction", error);
|
140
|
+
}
|
141
|
+
console.error("Error applying migration", error);
|
142
|
+
process.exit(1);
|
143
|
+
}
|
144
|
+
});
|
145
|
+
exports.applyMigration = applyMigration;
|
146
|
+
const applyUndoMigration = (client, schema, filename, script) => __awaiter(void 0, void 0, void 0, function* () {
|
147
|
+
try {
|
148
|
+
process.stdout.write(`Applying undo migration ${filename}... `);
|
149
|
+
yield client.query("BEGIN");
|
150
|
+
yield client.query(`SET search_path TO ${schema};
|
151
|
+
${script.toString()}`);
|
152
|
+
yield client.query(`INSERT INTO ${schema}.stepwise_migration_events (type, filename, script) VALUES ($1, $2, $3)`, ["undo", filename, script]);
|
153
|
+
yield client.query("COMMIT");
|
154
|
+
console.log(`done!`);
|
155
|
+
}
|
156
|
+
catch (error) {
|
157
|
+
try {
|
158
|
+
yield client.query("ROLLBACK");
|
159
|
+
}
|
160
|
+
catch (error) {
|
161
|
+
console.error("Error rolling back transaction", error);
|
162
|
+
}
|
163
|
+
console.error("Error applying undo migration", error);
|
164
|
+
process.exit(1);
|
165
|
+
}
|
121
166
|
});
|
122
|
-
exports.
|
167
|
+
exports.applyUndoMigration = applyUndoMigration;
|
@@ -0,0 +1,146 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
"use strict";
|
3
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
4
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
5
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
6
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
7
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
8
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
9
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
10
|
+
});
|
11
|
+
};
|
12
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
13
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
14
|
+
};
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
16
|
+
const yargs_1 = __importDefault(require("yargs"));
|
17
|
+
const db_1 = require("./db");
|
18
|
+
const state_1 = require("./state");
|
19
|
+
const utils_1 = require("./utils");
|
20
|
+
const main = () => __awaiter(void 0, void 0, void 0, function* () {
|
21
|
+
const argv = (0, yargs_1.default)(process.argv.slice(2)).argv;
|
22
|
+
(0, utils_1.validateArgs)(argv);
|
23
|
+
const schema = argv.schema;
|
24
|
+
const command = argv._[0];
|
25
|
+
const napply = argv.napply || Infinity;
|
26
|
+
const nundo = argv.nundo || 1;
|
27
|
+
const filePath = argv.path;
|
28
|
+
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
|
+
if (command === "migrate") {
|
32
|
+
if (!schemaExists) {
|
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));
|
54
|
+
}
|
55
|
+
else if (command === "info") {
|
56
|
+
if (!schemaExists) {
|
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
|
+
}
|
65
|
+
}
|
66
|
+
else if (command === "status") {
|
67
|
+
if (!schemaExists) {
|
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
|
+
}
|
76
|
+
}
|
77
|
+
else if (command === "validate") {
|
78
|
+
(0, utils_1.exitIfNotInitialized)(schemaExists, tableExists);
|
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);
|
85
|
+
}
|
86
|
+
else if (command === "drop") {
|
87
|
+
process.stdout.write(`Dropping the tables, schema and migration history table... `);
|
88
|
+
yield (0, db_1.dbDropAll)(client, schema);
|
89
|
+
console.log(`done!`);
|
90
|
+
}
|
91
|
+
else if (command === "undo") {
|
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
|
+
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);
|
107
|
+
}
|
108
|
+
else if (command === "audit") {
|
109
|
+
(0, utils_1.exitIfNotInitialized)(schemaExists, tableExists);
|
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
|
+
})));
|
119
|
+
}
|
120
|
+
else if (command === "get-applied-script") {
|
121
|
+
if (!schemaExists) {
|
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
|
+
}
|
137
|
+
}
|
138
|
+
else {
|
139
|
+
console.error(`Invalid command: ${command}`);
|
140
|
+
console.log(utils_1.usage);
|
141
|
+
process.exit(1);
|
142
|
+
}
|
143
|
+
client.release();
|
144
|
+
process.exit(0);
|
145
|
+
});
|
146
|
+
main();
|
@@ -0,0 +1,123 @@
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
13
|
+
};
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
15
|
+
exports.eventsToApplied = exports.getUndoFilename = exports.loadState = exports.validateMigrationFiles = void 0;
|
16
|
+
const git_diff_1 = __importDefault(require("git-diff"));
|
17
|
+
const path_1 = __importDefault(require("path"));
|
18
|
+
const db_1 = require("./db");
|
19
|
+
const utils_1 = require("./utils");
|
20
|
+
const validateMigrationFiles = (state) => {
|
21
|
+
let errors = [];
|
22
|
+
if (state.files.allFiles.length === 0) {
|
23
|
+
return ["No migration files found"];
|
24
|
+
}
|
25
|
+
if (state.current.appliedVersionedMigrations.length >
|
26
|
+
state.files.versionedFiles.length) {
|
27
|
+
errors.push("Migration history is in a bad state: more applied versioned migrations than files");
|
28
|
+
}
|
29
|
+
for (let i = 0; i < state.current.appliedVersionedMigrations.length; i++) {
|
30
|
+
const { filename, script } = state.files.versionedFiles[i];
|
31
|
+
if (state.current.appliedVersionedMigrations[i].filename !== filename) {
|
32
|
+
errors.push(`Migration history is in a bad state: applied versioned migration ${state.current.appliedVersionedMigrations[i].filename} does not match file ${filename}`);
|
33
|
+
break;
|
34
|
+
}
|
35
|
+
if (state.current.appliedVersionedMigrations[i].script !== script) {
|
36
|
+
errors.push(`Migration history is in a bad state: applied versioned migration ${state.current.appliedVersionedMigrations[i].filename} has been modified.\n\n` +
|
37
|
+
(0, git_diff_1.default)(state.current.appliedVersionedMigrations[i].script, script, {
|
38
|
+
color: true,
|
39
|
+
noHeaders: true,
|
40
|
+
}));
|
41
|
+
break;
|
42
|
+
}
|
43
|
+
}
|
44
|
+
if (state.current.appliedRepeatableMigrations.length >
|
45
|
+
state.files.repeatableFiles.length) {
|
46
|
+
errors.push("Migration history is in a bad state: more applied repeatable migrations than files");
|
47
|
+
}
|
48
|
+
for (let i = 0; i < state.current.appliedRepeatableMigrations.length; i++) {
|
49
|
+
const { filename } = state.files.repeatableFiles[i];
|
50
|
+
if (state.current.appliedRepeatableMigrations[i].filename !== filename) {
|
51
|
+
errors.push(`Migration history is in a bad state: applied repeatable migration ${state.current.appliedRepeatableMigrations[i].filename} does not match file ${filename}`);
|
52
|
+
break;
|
53
|
+
}
|
54
|
+
}
|
55
|
+
return errors;
|
56
|
+
};
|
57
|
+
exports.validateMigrationFiles = validateMigrationFiles;
|
58
|
+
const loadState = (client, schema, migrationPath) => __awaiter(void 0, void 0, void 0, function* () {
|
59
|
+
const events = yield (0, db_1.dbEventHistory)(client, schema);
|
60
|
+
const { appliedVersionedMigrations, appliedRepeatableMigrations, errors: appliedErrors, } = (0, exports.eventsToApplied)(events);
|
61
|
+
const { files: allFiles, errors: readFileErrors } = yield (0, utils_1.readMigrationFiles)(path_1.default.join(process.cwd(), migrationPath), appliedVersionedMigrations);
|
62
|
+
const unappliedVersionedFiles = allFiles
|
63
|
+
.filter((file) => file.type === "versioned")
|
64
|
+
.filter((file) => !appliedVersionedMigrations.find((event) => event.filename === file.filename));
|
65
|
+
const unappliedRepeatableFiles = allFiles
|
66
|
+
.filter((file) => file.type === "repeatable")
|
67
|
+
.filter((file) => !appliedRepeatableMigrations.find((event) => event.filename === file.filename && event.script === file.script));
|
68
|
+
return {
|
69
|
+
schema,
|
70
|
+
current: {
|
71
|
+
appliedVersionedMigrations,
|
72
|
+
appliedRepeatableMigrations,
|
73
|
+
},
|
74
|
+
events,
|
75
|
+
files: {
|
76
|
+
allFiles,
|
77
|
+
unappliedVersionedFiles,
|
78
|
+
unappliedRepeatableFiles,
|
79
|
+
versionedFiles: allFiles.filter((file) => file.type === "versioned"),
|
80
|
+
undoFiles: allFiles.filter((file) => file.type === "undo"),
|
81
|
+
repeatableFiles: allFiles.filter((file) => file.type === "repeatable"),
|
82
|
+
},
|
83
|
+
errors: appliedErrors.concat(readFileErrors),
|
84
|
+
};
|
85
|
+
});
|
86
|
+
exports.loadState = loadState;
|
87
|
+
const getUndoFilename = (filename) => {
|
88
|
+
return filename.replace(".sql", ".undo.sql");
|
89
|
+
};
|
90
|
+
exports.getUndoFilename = getUndoFilename;
|
91
|
+
const eventsToApplied = (events) => {
|
92
|
+
let errors = [];
|
93
|
+
let appliedVersionedMigrations = [];
|
94
|
+
let appliedRepeatableMigrations = [];
|
95
|
+
for (const event of events) {
|
96
|
+
if (event.type === "versioned") {
|
97
|
+
appliedVersionedMigrations.push(event);
|
98
|
+
}
|
99
|
+
else if (event.type === "undo") {
|
100
|
+
if (appliedVersionedMigrations.length === 0) {
|
101
|
+
errors.push("Events table is in a bad state: undo event without a migration to undo");
|
102
|
+
break;
|
103
|
+
}
|
104
|
+
else if ((0, exports.getUndoFilename)(appliedVersionedMigrations[appliedVersionedMigrations.length - 1]
|
105
|
+
.filename) !== event.filename) {
|
106
|
+
errors.push("Events table is in a bad state: down migration does not match the most recently applied migration");
|
107
|
+
break;
|
108
|
+
}
|
109
|
+
else {
|
110
|
+
appliedVersionedMigrations.pop();
|
111
|
+
}
|
112
|
+
}
|
113
|
+
else if (event.type === "repeatable") {
|
114
|
+
appliedRepeatableMigrations.push(event);
|
115
|
+
}
|
116
|
+
else {
|
117
|
+
errors.push(`Events table is in a bad state: unknown event type ${event.type}`);
|
118
|
+
break;
|
119
|
+
}
|
120
|
+
}
|
121
|
+
return { errors, appliedVersionedMigrations, appliedRepeatableMigrations };
|
122
|
+
};
|
123
|
+
exports.eventsToApplied = eventsToApplied;
|
@@ -0,0 +1,13 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.EventRow = void 0;
|
4
|
+
const zod_1 = require("zod");
|
5
|
+
const MigrationType = zod_1.z.enum(["versioned", "undo", "repeatable"]);
|
6
|
+
exports.EventRow = zod_1.z.object({
|
7
|
+
id: zod_1.z.number(),
|
8
|
+
type: MigrationType,
|
9
|
+
filename: zod_1.z.string(),
|
10
|
+
script: zod_1.z.string(),
|
11
|
+
applied_by: zod_1.z.string(),
|
12
|
+
applied_at: zod_1.z.string(),
|
13
|
+
});
|