@13w/miri 1.1.14 → 1.1.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.
- package/dist/cli.js +80 -7
- package/dist/miri.js +48 -2
- package/package.json +13 -13
package/dist/cli.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readFileSync, realpathSync } from 'node:fs';
|
|
1
2
|
import { readFile } from 'node:fs/promises';
|
|
2
3
|
Error.stackTraceLimit = Infinity;
|
|
3
4
|
import { join } from 'node:path';
|
|
@@ -9,21 +10,59 @@ import { createTunnel } from 'tunnel-ssh';
|
|
|
9
10
|
import SSHConfig from 'ssh-config';
|
|
10
11
|
import connection from './mongodb.js';
|
|
11
12
|
import Miri, { IndexStatus, PatchStatus } from './miri.js';
|
|
12
|
-
import { readFileSync } from 'fs';
|
|
13
|
-
import { realpathSync } from 'node:fs';
|
|
14
13
|
const pkg = await readFile(join(import.meta.dirname, '../package.json'), 'utf-8').then((content) => JSON.parse(content));
|
|
15
14
|
const mirirc = await readFile(join(process.cwd(), '.mirirc'), 'utf-8').then((content) => JSON.parse(content), () => ({}));
|
|
16
15
|
const { SSH_AUTH_SOCK, HOME } = process.env;
|
|
16
|
+
const askPassword = async (message = 'Password: ') => {
|
|
17
|
+
process.stdin.resume();
|
|
18
|
+
process.stdin.setEncoding('utf8');
|
|
19
|
+
process.stdin.setRawMode(true);
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
let password = '';
|
|
22
|
+
const readPass = function (chunk) {
|
|
23
|
+
const str = chunk.toString();
|
|
24
|
+
// backspace
|
|
25
|
+
if (str.charCodeAt(0) === 127) {
|
|
26
|
+
password = password.slice(0, -1);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
switch (str) {
|
|
30
|
+
case '\n':
|
|
31
|
+
case '\r':
|
|
32
|
+
case '\u0004':
|
|
33
|
+
// They've finished typing their password
|
|
34
|
+
process.stdin.setRawMode(false);
|
|
35
|
+
process.stdin.pause();
|
|
36
|
+
process.stdout.write('\n');
|
|
37
|
+
process.stdin.off('data', readPass);
|
|
38
|
+
resolve(password);
|
|
39
|
+
return;
|
|
40
|
+
case '\u0003':
|
|
41
|
+
// Ctrl C
|
|
42
|
+
process.stdin.off('data', readPass);
|
|
43
|
+
reject(new Error('Cancelled'));
|
|
44
|
+
default:
|
|
45
|
+
// More passsword characters
|
|
46
|
+
password += str;
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
process.stdout.write(message);
|
|
51
|
+
process.stdin.on('data', readPass);
|
|
52
|
+
});
|
|
53
|
+
};
|
|
17
54
|
const program = new Command();
|
|
18
55
|
program.version(pkg.version);
|
|
19
56
|
program.option('-e --env <environment>', 'Environment name from .mirirc', 'default');
|
|
20
57
|
program.option('-m --migrations <folder>', `Folder with migrations (default: "${process.cwd(), join(process.cwd(), 'migrations')}")`);
|
|
21
58
|
program.option('-d --db <mongo-uri>', 'MongoDB Connection URI (default: "mongodb://localhost:27017/test")');
|
|
59
|
+
program.option('--no-direct-connection', 'Disable direct connection', true);
|
|
22
60
|
program.option('--ssh-profile <profile>', 'Connect via SSH using profile');
|
|
23
61
|
program.option('--ssh-host <host>', 'Connect via SSH proxy Host');
|
|
24
62
|
program.option('--ssh-port <port>', 'Connect via SSH proxy Port');
|
|
25
63
|
program.option('--ssh-user <user>', 'Connect via SSH proxy User');
|
|
26
64
|
program.option('--ssh-key <path/to/key>', 'Connect via SSH proxy IdentityKey');
|
|
65
|
+
program.option('--ssh-ask-pass', 'Ask for the private key password');
|
|
27
66
|
let configCache;
|
|
28
67
|
const getConfig = (programOpts) => {
|
|
29
68
|
if (configCache) {
|
|
@@ -42,18 +81,26 @@ const getConfig = (programOpts) => {
|
|
|
42
81
|
config.sshKey = programOpts.sshKey ?? parsed.IdentityFile?.[0];
|
|
43
82
|
config.sshUser = programOpts.sshUser ?? parsed.User;
|
|
44
83
|
}
|
|
45
|
-
// console.dir(config)
|
|
46
84
|
return configCache = config;
|
|
47
85
|
};
|
|
48
86
|
const createSSHTunnel = async (opts) => {
|
|
49
87
|
const config = getConfig(opts);
|
|
88
|
+
const passphrase = config.sshKey && config.sshAskPass ? await askPassword() : void 0;
|
|
50
89
|
const sshOptions = {
|
|
51
90
|
host: config.sshHost,
|
|
52
91
|
port: Number(config.sshPort ?? 22),
|
|
53
92
|
username: config.sshUser,
|
|
54
|
-
agent: SSH_AUTH_SOCK,
|
|
55
|
-
privateKey: config.sshKey ? readFileSync(realpathSync(config.sshKey)) : void 0,
|
|
56
93
|
};
|
|
94
|
+
if (config.sshKey) {
|
|
95
|
+
sshOptions.privateKey = readFileSync(realpathSync(config.sshKey));
|
|
96
|
+
if (passphrase) {
|
|
97
|
+
sshOptions.passphrase = passphrase;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else if (SSH_AUTH_SOCK) {
|
|
101
|
+
sshOptions.agent = SSH_AUTH_SOCK;
|
|
102
|
+
}
|
|
103
|
+
console.dir([config, sshOptions]);
|
|
57
104
|
const dst = new URL(config.db);
|
|
58
105
|
const forwardOptions = {
|
|
59
106
|
dstPort: Number(dst.port ?? 27017),
|
|
@@ -69,7 +116,7 @@ const getMiri = async () => {
|
|
|
69
116
|
const config = getConfig(program.opts());
|
|
70
117
|
const db = config.sshHost ? await createSSHTunnel(config) : config.db;
|
|
71
118
|
const dbUri = new URL(db);
|
|
72
|
-
dbUri.searchParams.append('directConnection',
|
|
119
|
+
dbUri.searchParams.append('directConnection', String(config.directConnection ?? true));
|
|
73
120
|
dbUri.searchParams.append('appName', `miri+v${pkg.version}`);
|
|
74
121
|
const client = await connection(dbUri.toString());
|
|
75
122
|
return new Miri(client, {
|
|
@@ -152,11 +199,19 @@ initProgram.command('apply')
|
|
|
152
199
|
.argument('[patch]', 'patch name')
|
|
153
200
|
.option('--no-exec', 'Don\' execute patch, just set as done')
|
|
154
201
|
.option('--force', 'Force apply patch')
|
|
155
|
-
.action(async (
|
|
202
|
+
.action(async (patch, opts) => {
|
|
156
203
|
const miri = await getMiri();
|
|
157
204
|
await miri.init(opts, patch);
|
|
158
205
|
await miri[Symbol.asyncDispose]();
|
|
159
206
|
});
|
|
207
|
+
initProgram.command('remove')
|
|
208
|
+
.argument('<patch>', 'patch name')
|
|
209
|
+
.option('--no-exec', 'Don\' execute patch, just set as done')
|
|
210
|
+
.action(async (patch, opts) => {
|
|
211
|
+
const miri = await getMiri();
|
|
212
|
+
await miri.remove('init', patch, Boolean(opts?.exec));
|
|
213
|
+
await miri[Symbol.asyncDispose]();
|
|
214
|
+
});
|
|
160
215
|
initProgram.command('status')
|
|
161
216
|
.action(() => status(false, 'init'));
|
|
162
217
|
const indexesProgram = program.command('indexes')
|
|
@@ -215,5 +270,23 @@ patchProgram.command('sync')
|
|
|
215
270
|
await miri.sync(opts);
|
|
216
271
|
await miri[Symbol.asyncDispose]();
|
|
217
272
|
});
|
|
273
|
+
patchProgram.command('apply')
|
|
274
|
+
.argument('<group>', 'group name')
|
|
275
|
+
.argument('<patch>', 'patch name')
|
|
276
|
+
.option('--no-exec', 'Don\' execute patch, just set as done')
|
|
277
|
+
.action(async (group, patch, opts) => {
|
|
278
|
+
const miri = await getMiri();
|
|
279
|
+
await miri.applySingle(group, patch, Boolean(opts.exec));
|
|
280
|
+
await miri[Symbol.asyncDispose]();
|
|
281
|
+
});
|
|
282
|
+
patchProgram.command('remove')
|
|
283
|
+
.argument('<group>', 'group name')
|
|
284
|
+
.argument('<patch>', 'patch name')
|
|
285
|
+
.option('--no-exec', 'Don\' execute patch, just set as done')
|
|
286
|
+
.action(async (group, patch, opts) => {
|
|
287
|
+
const miri = await getMiri();
|
|
288
|
+
await miri.remove(group, patch, Boolean(opts.exec));
|
|
289
|
+
await miri[Symbol.asyncDispose]();
|
|
290
|
+
});
|
|
218
291
|
program.parse();
|
|
219
292
|
//# sourceMappingURL=cli.js.map
|
package/dist/miri.js
CHANGED
|
@@ -276,7 +276,7 @@ export default class Migrator {
|
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
}
|
|
279
|
-
async init({
|
|
279
|
+
async init({ exec = false, force = false } = {}, patch) {
|
|
280
280
|
const localInits = await this.getLocalPatches(true, 'init', patch);
|
|
281
281
|
const remoteInits = await this.getRemotePatches('init', patch);
|
|
282
282
|
if (!localInits.length) {
|
|
@@ -292,7 +292,7 @@ export default class Migrator {
|
|
|
292
292
|
console.groupEnd();
|
|
293
293
|
continue;
|
|
294
294
|
}
|
|
295
|
-
if (force ||
|
|
295
|
+
if (force || exec) {
|
|
296
296
|
console.log('applying');
|
|
297
297
|
await this.applyPatchContent({ body: patch.raw, hash: '' }, `${patch.group}/${patch.name}`, false);
|
|
298
298
|
}
|
|
@@ -322,5 +322,51 @@ export default class Migrator {
|
|
|
322
322
|
}
|
|
323
323
|
return patches;
|
|
324
324
|
}
|
|
325
|
+
async applySingle(group, name, exec = false) {
|
|
326
|
+
const [local] = await this.getLocalPatches(false, group, name);
|
|
327
|
+
const [remote] = await this.getRemotePatches(group, name);
|
|
328
|
+
if (!local) {
|
|
329
|
+
console.group(`Patch ${group}/${name} not found.`);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
console.group('Patch found');
|
|
333
|
+
if (exec && local.content.up) {
|
|
334
|
+
console.group(colors.cyan('applying migration...'));
|
|
335
|
+
await this.applyPatchContent(local.content.up, `${local.group}/${local.name}`);
|
|
336
|
+
console.log(colors.green('done'));
|
|
337
|
+
console.groupEnd();
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
console.log('set as done');
|
|
341
|
+
}
|
|
342
|
+
const _id = remote?._id ?? local._id;
|
|
343
|
+
await this.#collection.updateOne({ _id }, {
|
|
344
|
+
$set: {
|
|
345
|
+
_id,
|
|
346
|
+
group: local.group,
|
|
347
|
+
name: local.name,
|
|
348
|
+
content: local.content,
|
|
349
|
+
},
|
|
350
|
+
}, { upsert: true });
|
|
351
|
+
console.groupEnd();
|
|
352
|
+
}
|
|
353
|
+
async remove(group, name, exec = false) {
|
|
354
|
+
const [patch] = await this.getRemotePatches(group, name);
|
|
355
|
+
if (!patch) {
|
|
356
|
+
console.group(`Patch ${group}/${name} not found.`);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
console.group('Patch found, start removing');
|
|
360
|
+
if (exec && patch.content.down) {
|
|
361
|
+
console.group('De-applying patch...');
|
|
362
|
+
await this.applyPatchContent(patch.content.down, `${patch.group}/${patch.name}`);
|
|
363
|
+
console.log('done');
|
|
364
|
+
console.groupEnd();
|
|
365
|
+
}
|
|
366
|
+
console.group('Removing patch from database');
|
|
367
|
+
await this.#collection.deleteOne({ group, name });
|
|
368
|
+
console.log('done');
|
|
369
|
+
console.groupEnd();
|
|
370
|
+
}
|
|
325
371
|
}
|
|
326
372
|
//# sourceMappingURL=miri.js.map
|
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.16",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "v20"
|
|
@@ -25,24 +25,24 @@
|
|
|
25
25
|
"main": "bin/miri",
|
|
26
26
|
"types": "types/miri.d.ts",
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@mongosh/service-provider-server": "^2.2.
|
|
29
|
-
"@mongosh/shell-api": "^2.2.
|
|
30
|
-
"@mongosh/shell-evaluator": "^2.2.
|
|
28
|
+
"@mongosh/service-provider-server": "^2.2.15",
|
|
29
|
+
"@mongosh/shell-api": "^2.2.15",
|
|
30
|
+
"@mongosh/shell-evaluator": "^2.2.15",
|
|
31
31
|
"colors": "^1.4.0",
|
|
32
|
-
"commander": "^12.
|
|
33
|
-
"console-table-printer": "^2.12.
|
|
34
|
-
"mongodb": "^6.
|
|
35
|
-
"ssh-config": "^4.4.
|
|
32
|
+
"commander": "^12.1.0",
|
|
33
|
+
"console-table-printer": "^2.12.1",
|
|
34
|
+
"mongodb": "^6.8.0",
|
|
35
|
+
"ssh-config": "^4.4.4",
|
|
36
36
|
"tunnel-ssh": "^5.1.2"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@types/node": "^20.
|
|
40
|
-
"@typescript-eslint/eslint-plugin": "^7.
|
|
41
|
-
"@typescript-eslint/parser": "^7.
|
|
42
|
-
"eslint": "^
|
|
39
|
+
"@types/node": "^20.14.15",
|
|
40
|
+
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
41
|
+
"@typescript-eslint/parser": "^7.18.0",
|
|
42
|
+
"eslint": "^8.57.0",
|
|
43
43
|
"eslint-plugin-deprecation": "^2.0.0",
|
|
44
44
|
"ts-node": "^10.9.2",
|
|
45
|
-
"typescript": "^5.4
|
|
45
|
+
"typescript": "^5.5.4"
|
|
46
46
|
},
|
|
47
47
|
"license": "MIT",
|
|
48
48
|
"scripts": {
|