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.
Files changed (42) hide show
  1. package/README.md +20 -20
  2. package/dist/{db.js → src/db.js} +73 -28
  3. package/dist/src/index.js +146 -0
  4. package/dist/src/state.js +123 -0
  5. package/dist/src/types.js +13 -0
  6. package/dist/src/utils.js +184 -0
  7. package/dist/src/validate.js +1 -0
  8. package/dist/test/index.test.js +129 -0
  9. package/dist/test/utils.js +51 -0
  10. package/docker-compose.yml +21 -0
  11. package/package.json +12 -6
  12. package/src/db.ts +92 -37
  13. package/src/index.ts +115 -80
  14. package/src/state.ts +166 -0
  15. package/src/types.ts +49 -4
  16. package/src/utils.ts +122 -66
  17. package/test/index.test.ts +166 -0
  18. package/test/migrations-invalid/v0_get_number.repeatable.sql +6 -0
  19. package/test/migrations-invalid/v1_first.sql +1 -0
  20. package/test/migrations-invalid/v2_second.sql +4 -0
  21. package/test/migrations-invalid/v2_second.undo.sql +1 -0
  22. package/test/migrations-invalid/v3_third.sql +4 -0
  23. package/test/migrations-invalid/v3_third.undo.sql +1 -0
  24. package/test/migrations-template/v0_get_number.repeatable.sql +6 -0
  25. package/test/migrations-template/v1_first.sql +4 -0
  26. package/test/migrations-template/v2_second.sql +4 -0
  27. package/test/migrations-template/v2_second.undo.sql +1 -0
  28. package/test/migrations-template/v3_third.sql +4 -0
  29. package/test/migrations-template/v3_third.undo.sql +1 -0
  30. package/test/migrations-valid/v0_get_number.repeatable.sql +8 -0
  31. package/test/migrations-valid/v1_first.sql +4 -0
  32. package/test/migrations-valid/v2_second.sql +4 -0
  33. package/test/migrations-valid/v2_second.undo.sql +1 -0
  34. package/test/migrations-valid/v3_third.sql +4 -0
  35. package/test/migrations-valid/v3_third.undo.sql +1 -0
  36. package/test/utils.ts +69 -0
  37. package/tsconfig.json +1 -1
  38. package/dist/index.js +0 -115
  39. package/dist/migrate.js +0 -102
  40. package/dist/types.js +0 -2
  41. package/dist/utils.js +0 -132
  42. 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 `.down.sql`.
14
- e.g. `v1_users.sql` and `v1_users.down.sql`.
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
- down
25
+ undo
26
26
  Rollback the database to the previous version
27
27
  validate
28
- Validate the migration files and the migration history table
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 migration history table
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
- --nup Number of up migrations to apply (default: all)
44
- --ndown Number of down migrations to apply (default: 1)
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 migration history table... done!
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 history:
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
- ### Down
92
+ ### Undo
93
93
 
94
94
  Runs a single down migration for the last applied migration.
95
- Can run multiple down migrations if the `--ndown` option is provided.
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.down.sql... done!
111
+ Applying down migration v2_auth.undo.sql... done!
112
112
  All done! Applied 1 down migration
113
- Migration history:
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 migration history table.
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 history:
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.down.sql' │ 'postgres' │ '2024-11-24 05:41:34.541462' │
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 history:
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 migration history table.
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 history table... done!
283
+ Dropping the tables, schema and migration stepwise_migration_events table... done!
284
284
  ```
285
285
 
286
286
  </details>
@@ -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.dbGetScript = exports.dbCreateHistoryTable = exports.dbAuditHistory = exports.dbCreateSchema = exports.dbMigrationHistory = exports.dbTableExists = exports.dbHistorySchemaExists = exports.dbConnect = void 0;
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 dbHistorySchemaExists = (client, schema) => __awaiter(void 0, void 0, void 0, function* () {
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.dbHistorySchemaExists = dbHistorySchemaExists;
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 = 'stepwise_migrations' and schemaname = '${schema}')`);
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 dbMigrationHistory = (client, schema) => __awaiter(void 0, void 0, void 0, function* () {
81
- const migrationsQuery = yield client.query(`SELECT * FROM ${schema}.stepwise_migrations`);
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.dbMigrationHistory = dbMigrationHistory;
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 dbAuditHistory = (client, schema) => __awaiter(void 0, void 0, void 0, function* () {
92
- const auditQuery = yield client.query(`SELECT * FROM ${schema}.stepwise_audit`);
93
- return auditQuery.rows;
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.dbAuditHistory = dbAuditHistory;
96
- const dbCreateHistoryTable = (client, schema) => __awaiter(void 0, void 0, void 0, function* () {
97
- process.stdout.write(`Creating migration history table... `);
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}.stepwise_migrations (
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
- name TEXT UNIQUE NOT NULL,
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.dbCreateHistoryTable = dbCreateHistoryTable;
118
- const dbGetScript = (client, schema, filename) => __awaiter(void 0, void 0, void 0, function* () {
119
- const script = yield client.query(`SELECT script FROM ${schema}.stepwise_audit WHERE name = $1`, [filename]);
120
- return script.rows[0].script;
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.dbGetScript = dbGetScript;
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
+ });