knex-migrator 5.4.1 → 6.0.0

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 CHANGED
@@ -1,185 +1,155 @@
1
1
  # knex-migrator
2
2
 
3
- A database migration tool for [knex.js](https://github.com/tgriesser/knex), which supports MySQL and SQlite3.
3
+ `knex-migrator` is a database migration library and CLI built on [Knex](https://knexjs.org/) for projects that need ordered init scripts, versioned migrations, health checks, locking, and rollback support.
4
4
 
5
- ## Features
6
-
7
- - [x] JS API
8
- - [x] CLI Tool
9
- - [x] Differentiation between database initialization and migration (Support for a database schema, [like we use in Ghost](https://github.com/TryGhost/Ghost/blob/1.16.2/core/server/data/schema/schema.js))
10
- - [x] Support for database creation
11
- - [x] Hooks
12
- - [x] Rollback to latest version
13
- - [x] Auto-Rollback on error
14
- - [x] Database health check
15
- - [x] Supports transactions
16
- - [x] Full atomic, support for separate DML/DDL scripts (no autocommit)
17
- - [x] Migration lock
18
- - [x] Full debug & pretty log support
19
- - [x] Custom migration folder structure
20
- - [x] Stable (Used in [Ghost](https://github.com/TryGhost/Ghost) for many years in thousands of blogs in production mode)
5
+ It is maintained by Ghost and is used by [Ghost](https://github.com/TryGhost/Ghost) to run schema setup and migrations in production.
21
6
 
22
- # Install
7
+ ## Features
23
8
 
24
- `npm install knex-migrator --save`
9
+ - JavaScript API and CLI commands
10
+ - Separate database initialization and migration flows
11
+ - Database creation for MySQL-compatible targets
12
+ - Migration hooks for `init` and `migrate`
13
+ - Health checks against the current schema version
14
+ - Rollback to a previous migration version
15
+ - Automatic rollback when a migration fails
16
+ - Migration locking to avoid parallel runs
17
+ - Transaction support, including separate DML and DDL scripts
18
+ - Custom migration folder structure
19
+ - Debug logging with `DEBUG=knex-migrator:*`
25
20
 
26
- or
21
+ ## Install
27
22
 
28
- `yarn add knex-migrator`
23
+ ```bash
24
+ pnpm add knex-migrator
25
+ ```
29
26
 
30
- Add me to your globals:
31
- - `npm install --global knex-migrator`
27
+ `npm install knex-migrator --save` also works for npm-based projects. For global CLI usage:
32
28
 
33
- # Usage
29
+ ```bash
30
+ npm install --global knex-migrator
31
+ ```
34
32
 
35
- ## Pre-word
33
+ ## Requirements
36
34
 
37
- - Replicas are unsupported, because Knex.js [doesn't support them](https://github.com/tgriesser/knex/issues/2253).
38
- - Sqlite does **not** support read locks by default. Read [here](https://github.com/TryGhost/knex-migrator/issues/87) why.
39
- - [Comparison](https://github.com/TryGhost/knex-migrator/issues/119) with other available migration tools.
40
- - Don't mix DDL/DML statements in a migration script. In MySQL DDL statements use implicit commits.
41
- - It's highly recommended to write both the `up` and the `down` function to ensure a full rollback.
42
- - If your process dies while migrations are running, knex-migrator won't be able to release the migration lock.
43
- To release to lock you can run `knex-migrator rollback`. **But** it's recommended to check your database first to see in which state it is.
44
- You can check the tables `migrations` and `migrations_lock`. The rollback will rollback any migrations which were executed based on your current version.
35
+ - Node.js `^22.13.1 || ^24.14.1`
36
+ - A compatible Knex installation. `knex-migrator` first tries to load `knex` from the project path passed with `--mgpath` or `knexMigratorFilePath`, then falls back to its bundled Knex `2.4.2`.
37
+ - A database client supported by this package: `mysql`, `mysql2`, `sqlite3`, or `better-sqlite3`
45
38
 
46
- ## Configure knex-migrator
39
+ The `mysql` client name is accepted for backwards compatibility and is mapped to `mysql2`.
47
40
 
48
- The tool requires a config file in your project root.
49
- Please add a file named `MigratorConfig.js`. Knex-migrator will load the config file.
41
+ ## Configuration
50
42
 
43
+ Add `MigratorConfig.js` to the project root that will run migrations. CLI commands load this file from the current working directory by default, or from the directory passed with `--mgpath`.
51
44
 
52
- ```
45
+ ```js
53
46
  module.exports = {
54
47
  database: {
55
- client: String (Required) ['mysql', 'mysql2', 'sqlite3']
48
+ client: 'sqlite3',
56
49
  connection: {
57
- host: String, (Required) [e.g. '127.0.0.1']
58
- user: String, (Required)
59
- password: String, (Required)
60
- charset: String, (Optional) [Default: 'utf8mb4']
61
- database: String (Required)
50
+ filename: '/path/to/database.sqlite'
62
51
  }
63
52
  },
64
- migrationPath: String, (Required) [e.g. '/var/www/project/migrations']
65
- currentVersion: String, (Required) [e.g. '2.0']
66
- subfolder: String (Optional) [Default: 'versions']
67
- }
53
+ migrationPath: '/path/to/project/migrations',
54
+ currentVersion: '2.0',
55
+ subfolder: 'versions'
56
+ };
68
57
  ```
69
58
 
70
- Please take a look at [this real example](https://github.com/TryGhost/Ghost/blob/2.19.3/MigratorConfig.js).
59
+ For MySQL:
71
60
 
72
- ## Folder Structure
73
-
74
- ```
75
- project/
76
- migrations/
77
- hooks/
78
- init/
79
- index.js
80
- before.js
81
- shutdown.js
82
- migrate/
83
- index.js
84
- after.js
85
- shutdown.js
86
- init/
87
- 1-add-tables.js
88
- versions/
89
- 1.0/
90
- 1-add-events-table.js
91
- 2-normalise-settings.js
92
- 2.0/
93
- 1-add-timestamps-columns.js
94
- 2.1/
95
- 1-remove-empty-strings.js
96
- 2-add-webhooks-table.js
97
- 3-add-permissions.js
61
+ ```js
62
+ module.exports = {
63
+ database: {
64
+ client: 'mysql2',
65
+ connection: {
66
+ host: '127.0.0.1',
67
+ user: 'root',
68
+ password: 'root',
69
+ database: 'example',
70
+ charset: 'utf8mb4'
71
+ }
72
+ },
73
+ migrationPath: '/path/to/project/migrations',
74
+ currentVersion: '2.0'
75
+ };
98
76
  ```
99
77
 
100
- Please take a look at [this real example](https://github.com/TryGhost/Ghost/tree/2.19.3/core/server/data/migrations).
101
-
102
- ## Hooks
103
-
104
- Knex-migrator offers a couple of hooks, which makes it possible to hook into the migration process. You can create a hook per type: 'init' or 'migrate'. The folder name must be `hooks` and is not configurable. Please create an index.js file to export your functions, see [example](https://github.com/TryGhost/Ghost/blob/2.19.3/core/server/data/migrations/hooks/init/index.js).
105
-
106
- |hook|description|
107
- |---|---|
108
- |before|is called before anything happens|
109
- |beforeEach| is called before each migration script|
110
- |after|is called after everything happened|
111
- |afterEach|is called after each migration script|
112
- |shutdown|is called before the migrator shuts down|
113
-
78
+ `subfolder` is optional and defaults to `versions`.
114
79
 
115
- ## Migration Files
80
+ ## Migration Structure
116
81
 
117
- ### Config
118
- You can configure each migration script.
82
+ The default migration layout separates one-time init scripts from versioned migrations.
119
83
 
120
- ```
121
- module.exports.config = {
122
- transaction: Boolean
123
- }
124
- ```
125
-
126
-
127
- ### Examples
84
+ ```text
85
+ project/
86
+ migrations/
87
+ hooks/
88
+ init/
89
+ index.js
90
+ migrate/
91
+ index.js
92
+ init/
93
+ 1-add-tables.js
94
+ versions/
95
+ 1.0/
96
+ 1-add-events-table.js
97
+ 2-normalise-settings.js
98
+ 2.0/
99
+ 1-add-timestamps-columns.js
128
100
  ```
129
101
 
130
- module.exports.up = function(options) {
131
- const connection = options.connection;
102
+ Version folders are sorted in migration order. Each migration file can export `up`, `down`, and an optional `config` object.
132
103
 
133
- ...
134
-
135
- return Promise.resolve();
104
+ ```js
105
+ module.exports.config = {
106
+ transaction: true
136
107
  };
137
108
 
138
- module.exports.down = function(options) {
139
- const connection = options.connection;
140
-
141
- ...
142
-
143
- return Promise.resolve();
144
- }
145
- ```
146
-
147
- ```
109
+ module.exports.up = async function up(options) {
110
+ const connection = options.transacting || options.connection;
148
111
 
149
- module.exports.config = {
150
- transaction: true
112
+ await connection.schema.createTable('events', function (table) {
113
+ table.increments('id').primary();
114
+ table.string('name').notNullable();
115
+ });
151
116
  };
152
117
 
153
- module.exports.up = function(options) {
154
- const connection = options.transacting;
118
+ module.exports.down = async function down(options) {
119
+ const connection = options.transacting || options.connection;
155
120
 
156
- ...
157
-
158
- return Promise.resolve();
121
+ await connection.schema.dropTable('events');
159
122
  };
123
+ ```
160
124
 
161
- module.exports.down = function(options) {
162
- const connection = options.transacting;
125
+ Write both `up` and `down` whenever possible so failed migrations and manual rollbacks can return the database to a known state. Avoid mixing DDL and DML in one transactional migration for MySQL because DDL statements use implicit commits.
163
126
 
164
- ...
127
+ ## Hooks
165
128
 
166
- return Promise.resolve();
167
- }
168
- ```
129
+ Hooks live under `migrations/hooks/init` or `migrations/hooks/migrate`. Export any of these functions from the hook folder's `index.js`:
169
130
 
170
- ## CLI
131
+ | Hook | When it runs |
132
+ | --- | --- |
133
+ | `before` | Before the command starts running scripts |
134
+ | `beforeEach` | Before each migration script |
135
+ | `afterEach` | After each migration script |
136
+ | `after` | After all scripts finish |
137
+ | `shutdown` | Before the migrator disconnects |
171
138
 
172
- ### Commands
139
+ Hook functions receive the current Knex connection where applicable. `shutdown` receives `{executedFromShell}`.
173
140
 
174
- #### knex-migrator help
141
+ ## CLI
175
142
 
143
+ ```bash
144
+ knex-migrator --help
176
145
  ```
177
- $ knex-migrator help
146
+
147
+ ```text
178
148
  Usage: knex-migrator [options] [command]
179
149
 
180
150
  Options:
181
151
  -v, --version output the version number
182
- -h, --help output usage information
152
+ -h, --help display help for command
183
153
 
184
154
  Commands:
185
155
  init|i [config] init db
@@ -187,159 +157,114 @@ Commands:
187
157
  reset|r reset db
188
158
  health|h health of db
189
159
  rollback|ro rollbacks your db
190
- help [cmd] display help for [cmd]
160
+ help [command] display help for command
191
161
  ```
192
162
 
193
- #### knex-migrator health
194
-
195
- - Returns the database health/state
196
- - Based on your current version and your migration scripts
197
-
198
- #### knex-migrator init
199
-
200
- - Initializes your database based on your init scripts
201
- - Creates the database if it was not created yet
202
-
203
- ##### Options
204
-
205
- ```bash
206
- # Skips a specific migration script
207
- --skip
208
-
209
- # Runs only a specific migration script
210
- --only
211
-
212
- # Path to MigratorConfig.js
213
- --mgpath
214
- ```
215
-
216
- #### knex-migrator migrate
217
-
218
- - Migrates your database to latest version
219
- - Automatic rollback if an error occurs
220
-
221
- ##### Options
222
-
223
- ```bash
224
- # The version you would like to migrate to
225
- --v
226
-
227
- # Combo Feature to check whether the database was already initialized
228
- --init
229
-
230
- # Force the execution no matter which current version you are on
231
- --force
232
-
233
- # Path to MigratorConfig.js
234
- --mgpath
235
- ```
236
-
237
- #### knex-migrator rollback
238
-
239
- - Rolls back your database
240
- - By default, you can only rollback if the database is locked
241
-
242
- ##### Options
243
-
244
- ```bash
245
- # Ignores the migration lock
246
- --force
247
-
248
- # Version you would like to rollback to
249
- --v
250
- ```
251
-
252
- #### knex-migrator reset
253
-
254
- - Resets your database
255
- - Removes the database
256
-
257
- ##### Options
163
+ Common commands:
258
164
 
259
165
  ```bash
260
- # Ignores the migration lock
261
- --force
166
+ knex-migrator init --mgpath /path/to/project
167
+ knex-migrator migrate --mgpath /path/to/project
168
+ knex-migrator migrate --mgpath /path/to/project --v 2.0 --force
169
+ knex-migrator migrate --mgpath /path/to/project --init
170
+ knex-migrator rollback --mgpath /path/to/project --force --v 1.0
171
+ knex-migrator health --mgpath /path/to/project
172
+ knex-migrator reset --mgpath /path/to/project --force
262
173
  ```
263
174
 
264
- ### Advanced
175
+ `migrate --only <file>` can run one file within the target version when combined with `--v`. `init` supports `--only` and `--skip` for init scripts.
265
176
 
266
- `DEBUG=knex-migrator:* knex-migrator migrate`
177
+ If a process exits while migrations are running, the migration lock may remain in place. Inspect the `migrations` and `migrations_lock` tables before forcing rollback or reset.
267
178
 
268
-
269
- ## JS API
270
-
271
- ### Instantiation
179
+ ## JavaScript API
272
180
 
273
181
  ```js
274
182
  const KnexMigrator = require('knex-migrator');
275
183
 
276
- # Option 1: Pass path to MigratorConfig.js
277
- const knexMigrator = new KnexMigrator({
184
+ const migrator = new KnexMigrator({
278
185
  knexMigratorFilePath: process.cwd()
279
186
  });
280
-
281
- # Option 2: Pass object with config
282
- const knexMigrator = new KnexMigrator({
283
- knexMigratorConfig: { ... }
284
- });
285
-
286
187
  ```
287
188
 
288
- ### Commands
189
+ You can also pass config directly:
289
190
 
290
191
  ```js
291
- # Health
292
- knexMigrator.isDatabaseOK
293
-
294
- # Initialise database
295
- knexMigrator.init
296
-
297
- # Migrate database
298
- knexMigrator.migrate
192
+ const migrator = new KnexMigrator({
193
+ knexMigratorConfig: {
194
+ database: {
195
+ client: 'sqlite3',
196
+ connection: {
197
+ filename: '/path/to/database.sqlite'
198
+ }
199
+ },
200
+ migrationPath: '/path/to/project/migrations',
201
+ currentVersion: '2.0'
202
+ }
203
+ });
204
+ ```
299
205
 
300
- # Rollback database
301
- knexMigrator.rollback
206
+ Available methods:
302
207
 
303
- # Reset database
304
- knexMigrator.reset
305
- ```
208
+ - `init(options)`
209
+ - `migrate(options)`
210
+ - `rollback(options)`
211
+ - `reset(options)`
212
+ - `isDatabaseOK()`
306
213
 
307
- ### Examples
214
+ Example:
308
215
 
309
216
  ```js
310
- knexMigrator.isDatabaseOK()
311
- .then(function() {
312
- // database is OK
313
- // initialization & migrations are not missing
314
- })
315
- .catch(function(err) {
316
- if (err.code === 'DB_NOT_INITIALISED') {
317
- return knexMigrator.init();
318
- }
319
-
320
- if (err.code === 'DB_NEEDS_MIGRATION') {
321
- return knexMigrator.migrate();
322
- }
323
- });
217
+ migrator.isDatabaseOK()
218
+ .then(function () {
219
+ // Database is initialized and migrated.
220
+ })
221
+ .catch(function (err) {
222
+ if (err.code === 'DB_NOT_INITIALISED') {
223
+ return migrator.init();
224
+ }
225
+
226
+ if (err.code === 'DB_NEEDS_MIGRATION') {
227
+ return migrator.migrate();
228
+ }
324
229
 
230
+ throw err;
231
+ });
325
232
  ```
326
233
 
327
- # Test
234
+ ## Development
328
235
 
329
- - `yarn lint` run just eslint
330
- - `yarn test` run eslint && then tests
331
- - `NODE_ENV=testing-mysql yarn test` to test with MySQL
236
+ This repo uses pnpm and Corepack.
332
237
 
333
- # Publish
238
+ ```bash
239
+ corepack enable
240
+ pnpm install --frozen-lockfile
241
+ pnpm lint
242
+ pnpm test
243
+ pnpm coverage
244
+ ```
334
245
 
335
- - `yarn ship`
246
+ Useful test variants:
336
247
 
337
- # Copyright & License
248
+ ```bash
249
+ NODE_ENV=testing pnpm test
250
+ NODE_ENV=testing-better-sqlite3 pnpm test
251
+ NODE_ENV=testing-mysql pnpm test
252
+ ```
338
253
 
339
- Copyright (c) 2013-2026 Ghost Foundation - Released under the [MIT license](LICENSE).
254
+ CI runs linting, coverage on Node `22.13.1` and `24.14.1` across sqlite3, better-sqlite3, and MySQL 8, plus a Ghost consumer smoke test. The smoke test checks out Ghost, links this package into Ghost core, then runs Ghost init, health, rollback, migrate, and health checks through the CLI.
340
255
 
256
+ To run the Ghost smoke locally, place a Ghost checkout next to this repo or set `GHOST_CORE_PATH`:
341
257
 
258
+ ```bash
259
+ GHOST_CORE_PATH=/path/to/Ghost pnpm smoke:ghost
260
+ ```
342
261
 
262
+ ## Publish
343
263
 
264
+ ```bash
265
+ pnpm ship
266
+ ```
344
267
 
268
+ ## License
345
269
 
270
+ Copyright (c) 2013-2026 Ghost Foundation. Released under the [MIT license](LICENSE).
package/bin/knex-migrator CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- var program = require('commander'),
4
- pkg = require('../package.json');
3
+ const program = require('commander').program;
4
+ const pkg = require('../package.json');
5
5
 
6
6
  program
7
7
  .version(pkg.version, '-v, --version')
@@ -1,30 +1,35 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- var program = require('commander');
4
- var utils = require('../lib/utils');
5
-
6
- var logging = require('@tryghost/logging');
7
- var knexMigrator;
8
-
9
- utils.getKnexMigrator({path: process.cwd()})
10
- .then(function (KnexMigrator) {
11
- program
12
- .option('--mgpath <path>')
13
- .parse(process.argv);
14
-
15
- try {
16
- knexMigrator = new KnexMigrator({knexMigratorFilePath: program.mgpath, executedFromShell: true});
17
- } catch (err) {
18
- logging.error(err);
19
- process.exit(1);
20
- }
3
+ const program = require('commander').program;
4
+ const utils = require('../lib/utils');
5
+
6
+ const logging = require('@tryghost/logging');
7
+
8
+ async function main() {
9
+ const KnexMigrator = await utils.getKnexMigrator({path: process.cwd()});
10
+
11
+ program
12
+ .option('--mgpath <path>')
13
+ .parse(process.argv);
14
+
15
+ const options = program.opts();
16
+ let knexMigrator;
17
+
18
+ try {
19
+ knexMigrator = new KnexMigrator({
20
+ knexMigratorFilePath: options.mgpath,
21
+ executedFromShell: true,
22
+ });
23
+ } catch (err) {
24
+ logging.error(err);
25
+ process.exit(1);
26
+ }
27
+
28
+ await knexMigrator.isDatabaseOK();
29
+ logging.info('Woohoo, Database is healthy');
30
+ }
21
31
 
22
- return knexMigrator.isDatabaseOK()
23
- .then(function () {
24
- logging.info('Woohoo, Database is healthy');
25
- });
26
- })
27
- .catch(function (err) {
32
+ main().catch(function (err) {
28
33
  logging.error(err.message);
29
34
 
30
35
  if (err.help) {
@@ -1,34 +1,40 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- var program = require('commander');
4
- var utils = require('../lib/utils');
5
-
6
- var logging = require('@tryghost/logging');
7
- var knexMigrator;
8
-
9
- utils.getKnexMigrator({path: process.cwd()})
10
- .then(function (KnexMigrator) {
11
- program
12
- .option('--skip <item>')
13
- .option('--only <item>')
14
- .option('--mgpath <path>')
15
- .parse(process.argv);
16
-
17
- try {
18
- knexMigrator = new KnexMigrator({knexMigratorFilePath: program.mgpath, executedFromShell: true});
19
- } catch (err) {
20
- logging.error(err);
21
- process.exit(1);
22
- }
23
-
24
- return knexMigrator.init({
25
- skip: program.skip,
26
- only: program.only
27
- }).then(function () {
28
- logging.info('Finished database init!');
3
+ const program = require('commander').program;
4
+ const utils = require('../lib/utils');
5
+
6
+ const logging = require('@tryghost/logging');
7
+
8
+ async function main() {
9
+ const KnexMigrator = await utils.getKnexMigrator({path: process.cwd()});
10
+
11
+ program
12
+ .option('--skip <item>')
13
+ .option('--only <item>')
14
+ .option('--mgpath <path>')
15
+ .parse(process.argv);
16
+
17
+ const options = program.opts();
18
+ let knexMigrator;
19
+
20
+ try {
21
+ knexMigrator = new KnexMigrator({
22
+ knexMigratorFilePath: options.mgpath,
23
+ executedFromShell: true,
29
24
  });
30
- })
31
- .catch(function (err) {
25
+ } catch (err) {
26
+ logging.error(err);
27
+ process.exit(1);
28
+ }
29
+
30
+ await knexMigrator.init({
31
+ skip: options.skip,
32
+ only: options.only,
33
+ });
34
+ logging.info('Finished database init!');
35
+ }
36
+
37
+ main().catch(function (err) {
32
38
  logging.error(err);
33
39
  process.exit(1);
34
40
  });