Skip to content

Commit cf9746b

Browse files
committed
feat: add new command & export;
- creates a new migration file - injects timestamp or sequential (inferred) prefix - Closes #2
1 parent 529b2df commit cf9746b

File tree

6 files changed

+105
-11
lines changed

6 files changed

+105
-11
lines changed

bin.js

+10
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,14 @@ sade('ley')
3333
.option('-a, --all', 'Run all "down" migrations')
3434
.action(wrap('down'))
3535

36+
.command('new <filename>')
37+
.describe('Create a new migration file.')
38+
.option('-t, --timestamp', 'Prefix the filename with a timestamp')
39+
.option('-l, --length', 'The length of prefix, if not timestamp', 5)
40+
.action((filename, opts) => {
41+
opts.filename = filename;
42+
return wrap('new')(opts);
43+
})
44+
45+
3646
.parse(process.argv);

index.js

+32
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
const { join, resolve } = require('path');
2+
const { writeFileSync } = require('fs');
3+
const mkdirs = require('mk-dirs');
24
const $ = require('./lib/util');
35

46
async function parse(opts) {
@@ -68,3 +70,33 @@ exports.down = async function (opts={}) {
6870
if (client) await driver.end(client);
6971
}
7072
}
73+
74+
exports.new = async function (opts={}) {
75+
let { migrations } = await parse(opts);
76+
77+
let prefix = '';
78+
if (opts.timestamp) {
79+
prefix += Date.now() / 1e3 | 0;
80+
} else {
81+
let tmp = migrations.pop();
82+
if (tmp && /^\d+/.test(tmp.name)) {
83+
tmp = parseInt(tmp.name.match(/^\d+/)[0], 10);
84+
prefix = String(tmp + 1).padStart(opts.length, '0');
85+
} else {
86+
prefix = '0'.repeat(opts.length);
87+
}
88+
}
89+
90+
let filename = prefix + '-' + opts.filename.replace(/\s+/g, '-');
91+
if (!/\.\w+$/.test(filename)) filename += '.js';
92+
let dir = resolve(opts.cwd || '.', opts.dir);
93+
let file = join(dir, filename);
94+
95+
await mkdirs(dir).then(() => {
96+
let str = 'exports.up = async client => {\n\t// <insert magic here>\n};\n\n';
97+
str += 'exports.down = async client => {\n\t// just in case...\n};\n';
98+
writeFileSync(file, str);
99+
});
100+
101+
return filename;
102+
}

ley.d.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,20 @@ declare namespace Options {
1717
declare interface Down extends Base {
1818
all?: boolean;
1919
}
20+
21+
declare interface New extends Base {
22+
filename: string;
23+
timestamp?: boolean;
24+
length?: number;
25+
}
2026
}
2127

2228
declare class MigrationError extends Error {
2329
readonly migration: Record<'name'|'abs', string>;
2430
}
2531

26-
export function up(opts?: Options.Up): Promise<void>;
27-
export function down(opts?: Options.Down): Promise<void>;
32+
export function up(opts?: Options.Up): Promise<string[]>;
33+
export function down(opts?: Options.Down): Promise<string[]>;
34+
35+
declare function n(opts?: Options.New): Promise<string>;
36+
export { n as new };

lib/log.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ exports.info = msg => {
88
exports.done = (type, arr) => {
99
let msg = '';
1010

11-
if (arr.length > 0) {
11+
if (type === 'new') {
12+
msg += 'Created ' + green('1') + ' file:';
13+
msg += green().dim('\n ⌁ ') + underline().grey(arr);
14+
} else if (arr.length > 0) {
1215
let i=0, arrow = type === 'up' ? '↑' : '↓';
1316
msg += ('Migrated ' + green(arr.length) + (arr.length > 1 ? ' files:' : ' file:'));
1417
for (; i < arr.length; i++) msg += (green().dim(`\n ${arrow} `) + underline().grey(arr[i]));

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
},
2727
"dependencies": {
2828
"kleur": "^3.0.3",
29+
"mk-dirs": "^2.0.0",
2930
"sade": "^1.7.0",
3031
"totalist": "^1.0.1"
3132
},

readme.md

+47-8
Original file line numberDiff line numberDiff line change
@@ -77,21 +77,19 @@ Because of this, it's often recommended to prefix migrations with a timestamp or
7777
|-- 002-seats.js
7878
```
7979

80+
> **Note**: You may create the next file via `ley new todos --length 3` where `todos` is a meaningful name for your project.<br>The above command will create the `migrations/003-todos.js` filepath — or similar, depending on your command arguments.
81+
8082
***Timestamped***
8183

8284
```
8385
/migrations
84-
|-- 1581323445664-users.js
85-
|-- 1581323453868-teams.js
86-
|-- 1581323458383-seats.js
86+
|-- 1581323445-users.js
87+
|-- 1581323453-teams.js
88+
|-- 1581323458-seats.js
8789
```
8890

89-
**Tip:** Create timestamped migration files on the command line using `touch` and `date`:
91+
> **Note**: You may create the next file via `ley new todos --timestamp` where `todos` is a meaningful name for your project.<br>The above command will create the `migrations/1584389617-todos.js` filepath — or similar, depending on your command arguments.
9092
91-
```
92-
$ touch "$(date +%s)-users.js"
93-
#=> 1581785004-users.js
94-
```
9593

9694
**The order of your migrations is critically important!**<br>Migrations must be treated as an append-only immutable task chain. Without this, there's no way to _reliably_ rollback or recreate your database.
9795

@@ -239,6 +237,47 @@ Enable to apply **all** migration files' `down` task.<br>
239237
By default, only the most recently-applied migration file is invoked.
240238
241239
240+
### ley.new(opts?)
241+
Returns: `Promise<string>`
242+
243+
Returns the newly created _relative filename_ (eg, `000-users.js`).
244+
245+
#### opts.filename
246+
Type: `string`
247+
248+
**Required.** The name of the file to be created.
249+
250+
> **Note:** A prefix will be prepended based on [`opts.timestamp`](#optstimestamp) and [`opts.length`](#optslength) values.<br>The `.js` extension will be applied unless your input already has an extension.
251+
252+
#### opts.timestamp
253+
Type: `boolean`<br>
254+
Default: `false`
255+
256+
Should the migration file have a timestamped prefix?<br>
257+
If so, will use `Date.now()` floored to the nearest second.
258+
259+
#### opts.length
260+
Type: `number`<br>
261+
Default: `5`
262+
263+
When **not** using a timestamped prefix, this value controls the prefix total length.<br>
264+
For example, `00000-users.js` will be followed by `00001-teams.js`.
265+
266+
#### opts.cwd
267+
Type: `string`<br>
268+
Default: `.`
269+
270+
A target location to treat as the current working directory.
271+
272+
> **Note:** This value is `path.resolve()`d from the current `process.cwd()` location.
273+
274+
#### opts.dir
275+
Type: `string`<br>
276+
Default: `migrations`
277+
278+
The directory (relative to `opts.cwd`) to find migration files.
279+
280+
242281
## License
243282
244283
MIT © [Luke Edwards](https://lukeed.com)

0 commit comments

Comments
 (0)