1 | #!/usr/bin/env node
|
2 |
|
3 | const Liftoff = require('liftoff');
|
4 | const interpret = require('interpret');
|
5 | const path = require('path');
|
6 | const tildify = require('tildify');
|
7 | const commander = require('commander');
|
8 | const color = require('colorette');
|
9 | const argv = require('getopts')(process.argv.slice(2));
|
10 | const cliPkg = require('../package');
|
11 | const {
|
12 | mkConfigObj,
|
13 | resolveEnvironmentConfig,
|
14 | exit,
|
15 | success,
|
16 | checkLocalModule,
|
17 | getMigrationExtension,
|
18 | getSeedExtension,
|
19 | getStubPath,
|
20 | } = require('./utils/cli-config-utils');
|
21 | const { readFile, writeFile } = require('./../lib/util/fs');
|
22 |
|
23 | const { listMigrations } = require('./utils/migrationsLister');
|
24 |
|
25 | async function openKnexfile(configPath) {
|
26 | const importFile = require('../lib/util/import-file');
|
27 | let config = await importFile(configPath);
|
28 | if (config && config.default) {
|
29 | config = config.default;
|
30 | }
|
31 | if (typeof config === 'function') {
|
32 | config = await config();
|
33 | }
|
34 | return config;
|
35 | }
|
36 |
|
37 | async function initKnex(env, opts) {
|
38 | checkLocalModule(env);
|
39 | if (process.cwd() !== env.cwd) {
|
40 | process.chdir(env.cwd);
|
41 | console.log(
|
42 | 'Working directory changed to',
|
43 | color.magenta(tildify(env.cwd))
|
44 | );
|
45 | }
|
46 |
|
47 | env.configuration = env.configPath
|
48 | ? await openKnexfile(env.configPath)
|
49 | : mkConfigObj(opts);
|
50 |
|
51 | const resolvedConfig = resolveEnvironmentConfig(
|
52 | opts,
|
53 | env.configuration,
|
54 | env.configPath
|
55 | );
|
56 | const knex = require(env.modulePath);
|
57 | return knex(resolvedConfig);
|
58 | }
|
59 |
|
60 | function invoke(env) {
|
61 | env.modulePath = env.modulePath || env.knexpath || process.env.KNEX_PATH;
|
62 |
|
63 | const filetypes = ['js', 'coffee', 'ts', 'eg', 'ls'];
|
64 |
|
65 | const cliVersion = [
|
66 | color.blue('Knex CLI version:'),
|
67 | color.green(cliPkg.version),
|
68 | ].join(' ');
|
69 |
|
70 | const localVersion = [
|
71 | color.blue('Knex Local version:'),
|
72 | color.green(env.modulePackage.version || 'None'),
|
73 | ].join(' ');
|
74 |
|
75 | commander
|
76 | .version(`${cliVersion}\n${localVersion}`)
|
77 | .option('--debug', 'Run with debugging.')
|
78 | .option('--knexfile [path]', 'Specify the knexfile path.')
|
79 | .option('--knexpath [path]', 'Specify the path to knex instance.')
|
80 | .option('--cwd [path]', 'Specify the working directory.')
|
81 | .option('--client [name]', 'Set DB client without a knexfile.')
|
82 | .option('--connection [address]', 'Set DB connection without a knexfile.')
|
83 | .option(
|
84 | '--migrations-directory [path]',
|
85 | 'Set migrations directory without a knexfile.'
|
86 | )
|
87 | .option(
|
88 | '--migrations-table-name [path]',
|
89 | 'Set migrations table name without a knexfile.'
|
90 | )
|
91 | .option(
|
92 | '--env [name]',
|
93 | 'environment, default: process.env.NODE_ENV || development'
|
94 | )
|
95 | .option('--esm', 'Enable ESM interop.')
|
96 | .option('--specific [path]', 'Specify one seed file to execute.');
|
97 |
|
98 | commander
|
99 | .command('init')
|
100 | .description(' Create a fresh knexfile.')
|
101 | .option(
|
102 | `-x [${filetypes.join('|')}]`,
|
103 | 'Specify the knexfile extension (default js)'
|
104 | )
|
105 | .action(() => {
|
106 | const type = (argv.x || 'js').toLowerCase();
|
107 | if (filetypes.indexOf(type) === -1) {
|
108 | exit(`Invalid filetype specified: ${type}`);
|
109 | }
|
110 | if (env.configuration) {
|
111 | exit(`Error: ${env.knexfile} already exists`);
|
112 | }
|
113 | checkLocalModule(env);
|
114 | const stubPath = `./knexfile.${type}`;
|
115 | readFile(
|
116 | path.dirname(env.modulePath) +
|
117 | '/lib/migrate/stub/knexfile-' +
|
118 | type +
|
119 | '.stub'
|
120 | )
|
121 | .then((code) => {
|
122 | return writeFile(stubPath, code);
|
123 | })
|
124 | .then(() => {
|
125 | success(color.green(`Created ${stubPath}`));
|
126 | })
|
127 | .catch(exit);
|
128 | });
|
129 |
|
130 | commander
|
131 | .command('migrate:make <name>')
|
132 | .description(' Create a named migration file.')
|
133 | .option(
|
134 | `-x [${filetypes.join('|')}]`,
|
135 | 'Specify the stub extension (default js)'
|
136 | )
|
137 | .option(
|
138 | `--stub [<relative/path/from/knexfile>|<name>]`,
|
139 | 'Specify the migration stub to use. If using <name> the file must be located in config.migrations.directory'
|
140 | )
|
141 | .action(async (name) => {
|
142 | const opts = commander.opts();
|
143 | opts.client = opts.client || 'sqlite3';
|
144 | const instance = await initKnex(env, opts);
|
145 | const ext = getMigrationExtension(env, opts);
|
146 | const configOverrides = { extension: ext };
|
147 |
|
148 | const stub = getStubPath('migrations', env, opts);
|
149 | if (stub) {
|
150 | configOverrides.stub = stub;
|
151 | }
|
152 |
|
153 | instance.migrate
|
154 | .make(name, configOverrides)
|
155 | .then((name) => {
|
156 | success(color.green(`Created Migration: ${name}`));
|
157 | })
|
158 | .catch(exit);
|
159 | });
|
160 |
|
161 | commander
|
162 | .command('migrate:latest')
|
163 | .description(' Run all migrations that have not yet been run.')
|
164 | .option('--verbose', 'verbose')
|
165 | .action(async () => {
|
166 | try {
|
167 | const instance = await initKnex(env, commander.opts());
|
168 | const [batchNo, log] = await instance.migrate.latest();
|
169 | if (log.length === 0) {
|
170 | success(color.cyan('Already up to date'));
|
171 | }
|
172 | success(
|
173 | color.green(`Batch ${batchNo} run: ${log.length} migrations`) +
|
174 | (argv.verbose ? `\n${color.cyan(log.join('\n'))}` : '')
|
175 | );
|
176 | } catch (err) {
|
177 | exit(err);
|
178 | }
|
179 | });
|
180 |
|
181 | commander
|
182 | .command('migrate:up [<name>]')
|
183 | .description(
|
184 | ' Run the next or the specified migration that has not yet been run.'
|
185 | )
|
186 | .action((name) => {
|
187 | initKnex(env, commander.opts())
|
188 | .then((instance) => instance.migrate.up({ name }))
|
189 | .then(([batchNo, log]) => {
|
190 | if (log.length === 0) {
|
191 | success(color.cyan('Already up to date'));
|
192 | }
|
193 |
|
194 | success(
|
195 | color.green(
|
196 | `Batch ${batchNo} ran the following migrations:\n${log.join(
|
197 | '\n'
|
198 | )}`
|
199 | )
|
200 | );
|
201 | })
|
202 | .catch(exit);
|
203 | });
|
204 |
|
205 | commander
|
206 | .command('migrate:rollback')
|
207 | .description(' Rollback the last batch of migrations performed.')
|
208 | .option('--all', 'rollback all completed migrations')
|
209 | .option('--verbose', 'verbose')
|
210 | .action((cmd) => {
|
211 | const { all } = cmd;
|
212 |
|
213 | initKnex(env, commander.opts())
|
214 | .then((instance) => instance.migrate.rollback(null, all))
|
215 | .then(([batchNo, log]) => {
|
216 | if (log.length === 0) {
|
217 | success(color.cyan('Already at the base migration'));
|
218 | }
|
219 | success(
|
220 | color.green(
|
221 | `Batch ${batchNo} rolled back: ${log.length} migrations`
|
222 | ) + (argv.verbose ? `\n${color.cyan(log.join('\n'))}` : '')
|
223 | );
|
224 | })
|
225 | .catch(exit);
|
226 | });
|
227 |
|
228 | commander
|
229 | .command('migrate:down [<name>]')
|
230 | .description(
|
231 | ' Undo the last or the specified migration that was already run.'
|
232 | )
|
233 | .action((name) => {
|
234 | initKnex(env, commander.opts())
|
235 | .then((instance) => instance.migrate.down({ name }))
|
236 | .then(([batchNo, log]) => {
|
237 | if (log.length === 0) {
|
238 | success(color.cyan('Already at the base migration'));
|
239 | }
|
240 | success(
|
241 | color.green(
|
242 | `Batch ${batchNo} rolled back the following migrations:\n${log.join(
|
243 | '\n'
|
244 | )}`
|
245 | )
|
246 | );
|
247 | })
|
248 | .catch(exit);
|
249 | });
|
250 |
|
251 | commander
|
252 | .command('migrate:currentVersion')
|
253 | .description(' View the current version for the migration.')
|
254 | .action(() => {
|
255 | initKnex(env, commander.opts())
|
256 | .then((instance) => instance.migrate.currentVersion())
|
257 | .then((version) => {
|
258 | success(color.green('Current Version: ') + color.blue(version));
|
259 | })
|
260 | .catch(exit);
|
261 | });
|
262 |
|
263 | commander
|
264 | .command('migrate:list')
|
265 | .alias('migrate:status')
|
266 | .description(' List all migrations files with status.')
|
267 | .action(() => {
|
268 | initKnex(env, commander.opts())
|
269 | .then((instance) => {
|
270 | return instance.migrate.list();
|
271 | })
|
272 | .then(([completed, newMigrations]) => {
|
273 | listMigrations(completed, newMigrations);
|
274 | })
|
275 | .catch(exit);
|
276 | });
|
277 |
|
278 | commander
|
279 | .command('migrate:unlock')
|
280 | .description(' Forcibly unlocks the migrations lock table.')
|
281 | .action(() => {
|
282 | initKnex(env, commander.opts())
|
283 | .then((instance) => instance.migrate.forceFreeMigrationsLock())
|
284 | .then(() => {
|
285 | success(
|
286 | color.green(`Succesfully unlocked the migrations lock table`)
|
287 | );
|
288 | })
|
289 | .catch(exit);
|
290 | });
|
291 |
|
292 | commander
|
293 | .command('seed:make <name>')
|
294 | .description(' Create a named seed file.')
|
295 | .option(
|
296 | `-x [${filetypes.join('|')}]`,
|
297 | 'Specify the stub extension (default js)'
|
298 | )
|
299 | .option(
|
300 | `--stub [<relative/path/from/knexfile>|<name>]`,
|
301 | 'Specify the seed stub to use. If using <name> the file must be located in config.seeds.directory'
|
302 | )
|
303 | .action(async (name) => {
|
304 | const opts = commander.opts();
|
305 | opts.client = opts.client || 'sqlite3';
|
306 | const instance = await initKnex(env, opts);
|
307 | const ext = getSeedExtension(env, opts);
|
308 | const configOverrides = { extension: ext };
|
309 | const stub = getStubPath('seeds', env, opts);
|
310 | if (stub) {
|
311 | configOverrides.stub = stub;
|
312 | }
|
313 |
|
314 | instance.seed
|
315 | .make(name, configOverrides)
|
316 | .then((name) => {
|
317 | success(color.green(`Created seed file: ${name}`));
|
318 | })
|
319 | .catch(exit);
|
320 | });
|
321 |
|
322 | commander
|
323 | .command('seed:run')
|
324 | .description(' Run seed files.')
|
325 | .option('--verbose', 'verbose')
|
326 | .option('--specific', 'run specific seed file')
|
327 | .action(() => {
|
328 | initKnex(env, commander.opts())
|
329 | .then((instance) => instance.seed.run({ specific: argv.specific }))
|
330 | .then(([log]) => {
|
331 | if (log.length === 0) {
|
332 | success(color.cyan('No seed files exist'));
|
333 | }
|
334 | success(
|
335 | color.green(`Ran ${log.length} seed files`) +
|
336 | (argv.verbose ? `\n${color.cyan(log.join('\n'))}` : '')
|
337 | );
|
338 | })
|
339 | .catch(exit);
|
340 | });
|
341 |
|
342 | if (!process.argv.slice(2).length) {
|
343 | commander.outputHelp();
|
344 | }
|
345 |
|
346 | commander.parse(process.argv);
|
347 | }
|
348 |
|
349 | const cli = new Liftoff({
|
350 | name: 'knex',
|
351 | extensions: interpret.jsVariants,
|
352 | v8flags: require('v8flags'),
|
353 | moduleName: require('../package.json').name,
|
354 | });
|
355 |
|
356 | cli.on('require', function (name) {
|
357 | console.log('Requiring external module', color.magenta(name));
|
358 | });
|
359 |
|
360 | cli.on('requireFail', function (name) {
|
361 | console.log(color.red('Failed to load external module'), color.magenta(name));
|
362 | });
|
363 |
|
364 |
|
365 |
|
366 |
|
367 |
|
368 |
|
369 |
|
370 |
|
371 |
|
372 | if (argv.cwd) {
|
373 | process.chdir(argv.cwd);
|
374 | }
|
375 |
|
376 | if (argv.esm) {
|
377 |
|
378 |
|
379 | require = require('esm')(module);
|
380 |
|
381 | const ext = require.extensions['.js'];
|
382 | require.extensions['.js'] = (m, fileName) => {
|
383 | try {
|
384 |
|
385 |
|
386 | return ext(m, fileName);
|
387 | } catch (err) {
|
388 | if (err && err.code === 'ERR_REQUIRE_ESM') {
|
389 | return m._compile(
|
390 | require('fs').readFileSync(fileName, 'utf8'),
|
391 | fileName
|
392 | );
|
393 | }
|
394 | throw err;
|
395 | }
|
396 | };
|
397 | }
|
398 |
|
399 | cli.launch(
|
400 | {
|
401 | configPath: argv.knexfile,
|
402 | require: argv.require,
|
403 | completion: argv.completion,
|
404 | },
|
405 | invoke
|
406 | );
|