@13w/miri 1.1.8 → 1.1.10
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/dist/cli.js +53 -22
- package/dist/miri.js +18 -17
- package/dist/mongodb.js +1 -1
- package/package.json +7 -6
package/dist/cli.js
CHANGED
|
@@ -4,21 +4,52 @@ import { join } from 'node:path';
|
|
|
4
4
|
import { Command } from 'commander';
|
|
5
5
|
import { Table } from 'console-table-printer';
|
|
6
6
|
import colors from 'colors';
|
|
7
|
+
import { createTunnel } from 'tunnel-ssh';
|
|
7
8
|
import connection from './mongodb.js';
|
|
8
9
|
import Miri, { IndexStatus, PatchStatus } from './miri.js';
|
|
10
|
+
import { readFileSync } from 'fs';
|
|
11
|
+
import { realpathSync } from 'node:fs';
|
|
12
|
+
const { MIRI_MONGO_URI = 'mongodb://localhost:27017/test', SSH_AUTH_SOCK } = process.env;
|
|
9
13
|
const program = new Command();
|
|
10
14
|
program.option('-m --migrations <folder>', 'Folder with migrations', join(process.cwd(), 'migrations'));
|
|
11
|
-
program.option('-d --db <mongo-uri>', 'MongoDB Connection URI',
|
|
15
|
+
program.option('-d --db <mongo-uri>', 'MongoDB Connection URI', MIRI_MONGO_URI);
|
|
16
|
+
program.option('--ssh-host <host>', 'Connect via SSH proxy Host');
|
|
17
|
+
program.option('--ssh-port <port>', 'Connect via SSH proxy Port');
|
|
18
|
+
program.option('--ssh-user <user>', 'Connect via SSH proxy User');
|
|
19
|
+
program.option('--ssh-key <path/to/key>', 'Connect via SSH proxy IdentityKey');
|
|
20
|
+
const createSSHTunnel = async (opts) => {
|
|
21
|
+
const sshOptions = {
|
|
22
|
+
host: opts.sshHost,
|
|
23
|
+
port: Number(opts.sshPort ?? 22),
|
|
24
|
+
username: opts.sshUser,
|
|
25
|
+
agent: SSH_AUTH_SOCK,
|
|
26
|
+
privateKey: opts.sshKey ? readFileSync(realpathSync(opts.sshKey)) : void 0,
|
|
27
|
+
};
|
|
28
|
+
const dst = new URL(opts.db);
|
|
29
|
+
const forwardOptions = {
|
|
30
|
+
dstPort: Number(dst.port ?? 27017),
|
|
31
|
+
dstAddr: dst.hostname,
|
|
32
|
+
};
|
|
33
|
+
const [server] = await createTunnel({ autoClose: true }, {}, sshOptions, forwardOptions);
|
|
34
|
+
const addressInfo = server.address();
|
|
35
|
+
dst.host = addressInfo.family === 'IPv6' ? `[${String(addressInfo.address)}]` : String(addressInfo.address);
|
|
36
|
+
dst.port = String(addressInfo.port);
|
|
37
|
+
return dst.toString();
|
|
38
|
+
};
|
|
12
39
|
const getMiri = async () => {
|
|
13
40
|
const opts = program.opts();
|
|
14
|
-
const
|
|
41
|
+
const db = opts.sshHost ? await createSSHTunnel(opts) : opts.db;
|
|
42
|
+
const dbUri = new URL(db);
|
|
43
|
+
dbUri.searchParams.append('directConnection', 'true');
|
|
44
|
+
dbUri.searchParams.append('appName', 'miri+v1');
|
|
45
|
+
const client = await connection(dbUri.toString());
|
|
15
46
|
return new Miri(client, {
|
|
16
47
|
localMigrations: opts.migrations,
|
|
17
48
|
});
|
|
18
49
|
};
|
|
19
|
-
const status = async (remote = false) => {
|
|
50
|
+
const status = async (remote = false, group) => {
|
|
20
51
|
const miri = await getMiri();
|
|
21
|
-
const patches = await miri.stat(remote);
|
|
52
|
+
const patches = await miri.stat(remote, group);
|
|
22
53
|
await miri[Symbol.asyncDispose]();
|
|
23
54
|
if (!patches.length) {
|
|
24
55
|
return;
|
|
@@ -47,7 +78,8 @@ const status = async (remote = false) => {
|
|
|
47
78
|
program.command('status')
|
|
48
79
|
.description('Displays list of applied migrations')
|
|
49
80
|
.action(() => status(true));
|
|
50
|
-
const initProgram = program.command('init')
|
|
81
|
+
const initProgram = program.command('init')
|
|
82
|
+
.description('Manage initial scripts');
|
|
51
83
|
initProgram.command('apply')
|
|
52
84
|
.action(async () => {
|
|
53
85
|
const miri = await getMiri();
|
|
@@ -55,16 +87,13 @@ initProgram.command('apply')
|
|
|
55
87
|
await miri[Symbol.asyncDispose]();
|
|
56
88
|
});
|
|
57
89
|
initProgram.command('status')
|
|
58
|
-
.action(() =>
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const indexStatusProgram = indexesProgram.command('status')
|
|
90
|
+
.action(() => status(false, 'init'));
|
|
91
|
+
const indexesProgram = program.command('indexes')
|
|
92
|
+
.description('Manage indexes');
|
|
93
|
+
indexesProgram.command('status')
|
|
63
94
|
.argument('[collection]', 'MongoDB Collection name')
|
|
64
95
|
.option('-q --quiet', 'Show only changes', false)
|
|
65
|
-
.action(async () => {
|
|
66
|
-
const collection = indexStatusProgram.args[0];
|
|
67
|
-
const { quiet } = indexStatusProgram.opts();
|
|
96
|
+
.action(async (collection, { quiet }) => {
|
|
68
97
|
const miri = await getMiri();
|
|
69
98
|
const structure = await miri.indexesDiff(collection);
|
|
70
99
|
await miri[Symbol.asyncDispose]();
|
|
@@ -94,12 +123,12 @@ const indexStatusProgram = indexesProgram.command('status')
|
|
|
94
123
|
}
|
|
95
124
|
});
|
|
96
125
|
const indexSyncProgram = indexesProgram.command('sync');
|
|
97
|
-
indexSyncProgram.
|
|
98
|
-
.action(async () => {
|
|
126
|
+
indexSyncProgram.argument('[collection]', 'MongoDB Collection name', '')
|
|
127
|
+
.action(async (coll) => {
|
|
99
128
|
const miri = await getMiri();
|
|
100
129
|
let group = '';
|
|
101
|
-
console.group('Starting synchronisation...'
|
|
102
|
-
for await (const { collection, status, name, error } of miri.indexesSync()) {
|
|
130
|
+
console.group('Starting synchronisation...');
|
|
131
|
+
for await (const { collection, status, name, error } of miri.indexesSync(coll)) {
|
|
103
132
|
if (group !== collection) {
|
|
104
133
|
if (group) {
|
|
105
134
|
console.log('Done');
|
|
@@ -120,17 +149,19 @@ indexSyncProgram.option('<collection>', 'MongoDB Collection name', '')
|
|
|
120
149
|
console.groupEnd();
|
|
121
150
|
await miri[Symbol.asyncDispose]();
|
|
122
151
|
});
|
|
123
|
-
program.command('
|
|
152
|
+
const patchProgram = program.command('patch')
|
|
153
|
+
.description('Applies patch to database');
|
|
154
|
+
patchProgram.command('diff')
|
|
124
155
|
.description('Displays difference between local and applied migrations')
|
|
125
156
|
.action(() => status());
|
|
126
|
-
|
|
157
|
+
patchProgram.command('sync')
|
|
158
|
+
.description('Applies migrations')
|
|
127
159
|
.option('--remote', 'Remote only')
|
|
128
160
|
.option('--degraded', 'Re-apply patches on degraded migrations')
|
|
129
161
|
.option('--all', 'Re-apply all patches')
|
|
130
|
-
.
|
|
131
|
-
.action(async () => {
|
|
162
|
+
.action(async (opts) => {
|
|
132
163
|
const miri = await getMiri();
|
|
133
|
-
await miri.sync(
|
|
164
|
+
await miri.sync(opts);
|
|
134
165
|
await miri[Symbol.asyncDispose]();
|
|
135
166
|
});
|
|
136
167
|
program.parse();
|
package/dist/miri.js
CHANGED
|
@@ -22,9 +22,9 @@ export var IndexStatus;
|
|
|
22
22
|
})(IndexStatus || (IndexStatus = {}));
|
|
23
23
|
const sortPatches = (a, b) => {
|
|
24
24
|
if (a.group === b.group) {
|
|
25
|
-
return a.name
|
|
25
|
+
return a.name.localeCompare(b.name);
|
|
26
26
|
}
|
|
27
|
-
return a.group
|
|
27
|
+
return a.group.localeCompare(b.group);
|
|
28
28
|
};
|
|
29
29
|
export default class Migrator {
|
|
30
30
|
client;
|
|
@@ -42,9 +42,9 @@ export default class Migrator {
|
|
|
42
42
|
async [Symbol.asyncDispose]() {
|
|
43
43
|
await this.#client.close();
|
|
44
44
|
}
|
|
45
|
-
async diff() {
|
|
46
|
-
const localPatches = await this.getLocalPatches();
|
|
47
|
-
const remotePatches = await this.getRemotePatches();
|
|
45
|
+
async diff(group) {
|
|
46
|
+
const localPatches = await this.getLocalPatches(false, group);
|
|
47
|
+
const remotePatches = await this.getRemotePatches(group);
|
|
48
48
|
for (const localPatch of localPatches) {
|
|
49
49
|
const remotePatch = remotePatches.find(({ group, name }) => localPatch.group === group && localPatch.name === name);
|
|
50
50
|
if (remotePatch) {
|
|
@@ -93,29 +93,29 @@ export default class Migrator {
|
|
|
93
93
|
const filename = `${patch.group}/${patch.name}`;
|
|
94
94
|
console.group(`Patch ${patch.group} / ${patch.name}...`);
|
|
95
95
|
if (patch.status === PatchStatus.Removed) {
|
|
96
|
-
console.group('
|
|
97
|
-
const result = await this.applyPatchContent(patch.remoteContent?.down, filename);
|
|
98
|
-
console.log(result === -1 ? '
|
|
96
|
+
console.group(colors.cyan('reverting changes'));
|
|
97
|
+
const result = await this.applyPatchContent((patch.remoteContent ?? patch.content)?.down, filename);
|
|
98
|
+
console.log(result === -1 ? colors.white('revert script not found') : colors.green('done'));
|
|
99
99
|
console.groupEnd();
|
|
100
100
|
await this.#collection.deleteOne({ _id: patch._id });
|
|
101
101
|
console.groupEnd();
|
|
102
102
|
continue;
|
|
103
103
|
}
|
|
104
104
|
if (patch.status === PatchStatus.Changed) {
|
|
105
|
-
console.group('
|
|
106
|
-
const result = await this.applyPatchContent(patch.remoteContent?.down, filename);
|
|
107
|
-
console.log(result === -1 ? '
|
|
105
|
+
console.group(colors.cyan('reverting changes'));
|
|
106
|
+
const result = await this.applyPatchContent((patch.remoteContent ?? patch.content)?.down, filename);
|
|
107
|
+
console.log(result === -1 ? colors.white('revert script not found') : colors.green('done'));
|
|
108
108
|
console.groupEnd();
|
|
109
109
|
patch.status = PatchStatus.New;
|
|
110
110
|
}
|
|
111
111
|
if (all || degraded || patch.status === PatchStatus.New) {
|
|
112
|
-
console.group(colors.white('
|
|
112
|
+
console.group(colors.white('resting migration'));
|
|
113
113
|
const test = await this.applyPatchContent(patch.content.test, filename);
|
|
114
114
|
console.log(colors.white(`degradation level: ${test}`));
|
|
115
115
|
if (all || test !== 0) {
|
|
116
|
-
console.group(colors.cyan('
|
|
116
|
+
console.group(colors.cyan('applying migration...'));
|
|
117
117
|
await this.applyPatchContent(patch.content.up, filename);
|
|
118
|
-
console.log(colors.green('
|
|
118
|
+
console.log(colors.green('done'));
|
|
119
119
|
console.groupEnd();
|
|
120
120
|
}
|
|
121
121
|
else {
|
|
@@ -166,7 +166,8 @@ export default class Migrator {
|
|
|
166
166
|
patchObject.raw = patchContent.toString('base64');
|
|
167
167
|
continue;
|
|
168
168
|
}
|
|
169
|
-
const patchExports = await evaluateJs(patchContent.toString())
|
|
169
|
+
const patchExports = await evaluateJs(patchContent.toString())
|
|
170
|
+
.catch(() => ({}));
|
|
170
171
|
for (const key of ['test', 'up', 'down']) {
|
|
171
172
|
const func = patchExports[key];
|
|
172
173
|
if (typeof func !== 'function') {
|
|
@@ -302,8 +303,8 @@ export default class Migrator {
|
|
|
302
303
|
}
|
|
303
304
|
console.groupEnd();
|
|
304
305
|
}
|
|
305
|
-
async stat(remote = false) {
|
|
306
|
-
const patches = await (remote ? this.getRemotePatches() : this.diff());
|
|
306
|
+
async stat(remote = false, group) {
|
|
307
|
+
const patches = await (remote ? this.getRemotePatches(group) : this.diff(group));
|
|
307
308
|
for (const patch of patches) {
|
|
308
309
|
patch.status = patch.status ?? PatchStatus.Ok;
|
|
309
310
|
const degradation = await this.applyPatchContent(patch.content.test, `${patch.group}/${patch.name}`);
|
package/dist/mongodb.js
CHANGED
|
@@ -2,7 +2,7 @@ import { MongoClient } from 'mongodb';
|
|
|
2
2
|
let client;
|
|
3
3
|
export default async function connect(uri = 'mongodb://localhost:27017/test') {
|
|
4
4
|
if (!client) {
|
|
5
|
-
client = await
|
|
5
|
+
client = await MongoClient.connect(uri);
|
|
6
6
|
client?.topology?.socket?.unref();
|
|
7
7
|
}
|
|
8
8
|
return client;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@13w/miri",
|
|
3
3
|
"description": "MongoDB patch manager",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.10",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "v20"
|
|
@@ -26,16 +26,17 @@
|
|
|
26
26
|
"colors": "^1.4.0",
|
|
27
27
|
"commander": "^12.0.0",
|
|
28
28
|
"console-table-printer": "^2.12.0",
|
|
29
|
-
"mongodb": "^6.
|
|
29
|
+
"mongodb": "^6.5.0",
|
|
30
|
+
"tunnel-ssh": "^5.1.2"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
|
-
"@types/node": "^20.11.
|
|
33
|
-
"@typescript-eslint/eslint-plugin": "^7.
|
|
34
|
-
"@typescript-eslint/parser": "^7.
|
|
33
|
+
"@types/node": "^20.11.27",
|
|
34
|
+
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
|
35
|
+
"@typescript-eslint/parser": "^7.2.0",
|
|
35
36
|
"eslint": "^8.57.0",
|
|
36
37
|
"eslint-plugin-deprecation": "^2.0.0",
|
|
37
38
|
"ts-node": "^10.9.2",
|
|
38
|
-
"typescript": "^5.
|
|
39
|
+
"typescript": "^5.4.2"
|
|
39
40
|
},
|
|
40
41
|
"license": "MIT",
|
|
41
42
|
"scripts": {
|