@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.
Files changed (3) hide show
  1. package/dist/cli.js +80 -7
  2. package/dist/miri.js +48 -2
  3. 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', 'true');
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 (opts, patch) => {
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({ noExec = false, force = false } = {}, patch) {
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 || !noExec) {
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.14",
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.5",
29
- "@mongosh/shell-api": "^2.2.5",
30
- "@mongosh/shell-evaluator": "^2.2.5",
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.0.0",
33
- "console-table-printer": "^2.12.0",
34
- "mongodb": "^6.5.0",
35
- "ssh-config": "^4.4.3",
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.12.7",
40
- "@typescript-eslint/eslint-plugin": "^7.8.0",
41
- "@typescript-eslint/parser": "^7.8.0",
42
- "eslint": "^9.1.1",
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.5"
45
+ "typescript": "^5.5.4"
46
46
  },
47
47
  "license": "MIT",
48
48
  "scripts": {