@13w/miri 1.1.9 → 1.1.11

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 CHANGED
@@ -1,19 +1,79 @@
1
+ import { readFile } from 'node:fs/promises';
1
2
  Error.stackTraceLimit = Infinity;
2
3
  import { join } from 'node:path';
3
4
  /* eslint-disable no-console */
4
5
  import { Command } from 'commander';
5
6
  import { Table } from 'console-table-printer';
6
7
  import colors from 'colors';
8
+ import { createTunnel } from 'tunnel-ssh';
9
+ import SSHConfig from 'ssh-config';
7
10
  import connection from './mongodb.js';
8
11
  import Miri, { IndexStatus, PatchStatus } from './miri.js';
12
+ import { readFileSync } from 'fs';
13
+ import { realpathSync } from 'node:fs';
14
+ const pkg = await readFile(join(import.meta.dirname, '../package.json'), 'utf-8').then((content) => JSON.parse(content));
15
+ const mirirc = await readFile(join(process.cwd(), '.mirirc'), 'utf-8').then((content) => JSON.parse(content), () => ({}));
16
+ const { SSH_AUTH_SOCK, HOME } = process.env;
9
17
  const program = new Command();
10
- program.option('-m --migrations <folder>', 'Folder with migrations', join(process.cwd(), 'migrations'));
11
- program.option('-d --db <mongo-uri>', 'MongoDB Connection URI', 'mongodb://localhost:27017/test');
18
+ program.version(pkg.version);
19
+ program.option('-e --env <environment>', 'Environment name from .mirirc', 'default');
20
+ program.option('-m --migrations <folder>', `Folder with migrations (default: "${process.cwd(), join(process.cwd(), 'migrations')}")`);
21
+ program.option('-d --db <mongo-uri>', 'MongoDB Connection URI (default: "mongodb://localhost:27017/test")');
22
+ program.option('--ssh-profile <profile>', 'Connect via SSH using profile');
23
+ program.option('--ssh-host <host>', 'Connect via SSH proxy Host');
24
+ program.option('--ssh-port <port>', 'Connect via SSH proxy Port');
25
+ program.option('--ssh-user <user>', 'Connect via SSH proxy User');
26
+ program.option('--ssh-key <path/to/key>', 'Connect via SSH proxy IdentityKey');
27
+ let configCache;
28
+ const getConfig = (programOpts) => {
29
+ if (configCache) {
30
+ return configCache;
31
+ }
32
+ const envs = mirirc.environments ?? { default: mirirc };
33
+ const env = envs[programOpts.env ?? 'default'] ?? {};
34
+ const config = Object.assign({}, mirirc, env, programOpts);
35
+ if (config.sshProfile) {
36
+ const sshConfigPath = join(HOME ?? '', '.ssh/config');
37
+ const sshConfigContent = readFileSync(sshConfigPath, 'utf-8');
38
+ console.log(`Reading profile ${config.sshProfile} from SSH config ${sshConfigPath}`);
39
+ const parsed = SSHConfig.parse(sshConfigContent).compute(config.sshProfile);
40
+ config.sshHost = programOpts.sshHost ?? parsed.Hostname;
41
+ config.sshPort = programOpts.sshPort ?? parsed.Port;
42
+ config.sshKey = programOpts.sshKey ?? parsed.IdentityFile?.[0];
43
+ config.sshUser = programOpts.sshUser ?? parsed.User;
44
+ }
45
+ // console.dir(config)
46
+ return configCache = config;
47
+ };
48
+ const createSSHTunnel = async (opts) => {
49
+ const config = getConfig(opts);
50
+ const sshOptions = {
51
+ host: config.sshHost,
52
+ port: Number(config.sshPort ?? 22),
53
+ username: config.sshUser,
54
+ agent: SSH_AUTH_SOCK,
55
+ privateKey: config.sshKey ? readFileSync(realpathSync(config.sshKey)) : void 0,
56
+ };
57
+ const dst = new URL(config.db);
58
+ const forwardOptions = {
59
+ dstPort: Number(dst.port ?? 27017),
60
+ dstAddr: dst.hostname,
61
+ };
62
+ const [server] = await createTunnel({ autoClose: true }, {}, sshOptions, forwardOptions);
63
+ const addressInfo = server.address();
64
+ dst.host = addressInfo.family === 'IPv6' ? `[${String(addressInfo.address)}]` : String(addressInfo.address);
65
+ dst.port = String(addressInfo.port);
66
+ return dst.toString();
67
+ };
12
68
  const getMiri = async () => {
13
- const opts = program.opts();
14
- const client = await connection(opts.db);
69
+ const config = getConfig(program.opts());
70
+ const db = config.sshHost ? await createSSHTunnel(config) : config.db;
71
+ const dbUri = new URL(db);
72
+ dbUri.searchParams.append('directConnection', 'true');
73
+ dbUri.searchParams.append('appName', `miri+v${pkg.version}`);
74
+ const client = await connection(dbUri.toString());
15
75
  return new Miri(client, {
16
- localMigrations: opts.migrations,
76
+ localMigrations: config.migrations,
17
77
  });
18
78
  };
19
79
  const status = async (remote = false, group) => {
@@ -59,12 +119,10 @@ initProgram.command('status')
59
119
  .action(() => status(false, 'init'));
60
120
  const indexesProgram = program.command('indexes')
61
121
  .description('Manage indexes');
62
- const indexStatusProgram = indexesProgram.command('status')
122
+ indexesProgram.command('status')
63
123
  .argument('[collection]', 'MongoDB Collection name')
64
124
  .option('-q --quiet', 'Show only changes', false)
65
- .action(async () => {
66
- const collection = indexStatusProgram.args[0];
67
- const { quiet } = indexStatusProgram.opts();
125
+ .action(async (collection, { quiet }) => {
68
126
  const miri = await getMiri();
69
127
  const structure = await miri.indexesDiff(collection);
70
128
  await miri[Symbol.asyncDispose]();
@@ -94,12 +152,12 @@ const indexStatusProgram = indexesProgram.command('status')
94
152
  }
95
153
  });
96
154
  const indexSyncProgram = indexesProgram.command('sync');
97
- indexSyncProgram.option('<collection>', 'MongoDB Collection name', '')
98
- .action(async () => {
155
+ indexSyncProgram.argument('[collection]', 'MongoDB Collection name', '')
156
+ .action(async (coll) => {
99
157
  const miri = await getMiri();
100
158
  let group = '';
101
159
  console.group('Starting synchronisation...');
102
- for await (const { collection, status, name, error } of miri.indexesSync(...indexSyncProgram.args)) {
160
+ for await (const { collection, status, name, error } of miri.indexesSync(coll)) {
103
161
  if (group !== collection) {
104
162
  if (group) {
105
163
  console.log('Done');
@@ -122,17 +180,17 @@ indexSyncProgram.option('<collection>', 'MongoDB Collection name', '')
122
180
  });
123
181
  const patchProgram = program.command('patch')
124
182
  .description('Applies patch to database');
125
- const patchDiffProgram = patchProgram.command('diff')
183
+ patchProgram.command('diff')
126
184
  .description('Displays difference between local and applied migrations')
127
185
  .action(() => status());
128
- const patchSyncProgram = patchProgram.command('sync')
186
+ patchProgram.command('sync')
129
187
  .description('Applies migrations')
130
188
  .option('--remote', 'Remote only')
131
189
  .option('--degraded', 'Re-apply patches on degraded migrations')
132
190
  .option('--all', 'Re-apply all patches')
133
- .action(async () => {
191
+ .action(async (opts) => {
134
192
  const miri = await getMiri();
135
- await miri.sync(patchSyncProgram.opts());
193
+ await miri.sync(opts);
136
194
  await miri[Symbol.asyncDispose]();
137
195
  });
138
196
  program.parse();
package/dist/mongodb.js CHANGED
@@ -1,8 +1,10 @@
1
+ /* eslint-disable no-console */
1
2
  import { MongoClient } from 'mongodb';
2
3
  let client;
3
4
  export default async function connect(uri = 'mongodb://localhost:27017/test') {
4
5
  if (!client) {
5
- client = await new MongoClient(uri).connect();
6
+ console.log(`Connecting MongoDB to ${uri}`);
7
+ client = await MongoClient.connect(uri);
6
8
  client?.topology?.socket?.unref();
7
9
  }
8
10
  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.9",
4
+ "version": "1.1.11",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": "v20"
@@ -26,16 +26,18 @@
26
26
  "colors": "^1.4.0",
27
27
  "commander": "^12.0.0",
28
28
  "console-table-printer": "^2.12.0",
29
- "mongodb": "^6.3.0"
29
+ "mongodb": "^6.5.0",
30
+ "ssh-config": "^4.4.2",
31
+ "tunnel-ssh": "^5.1.2"
30
32
  },
31
33
  "devDependencies": {
32
- "@types/node": "^20.11.21",
33
- "@typescript-eslint/eslint-plugin": "^7.1.0",
34
- "@typescript-eslint/parser": "^7.1.0",
34
+ "@types/node": "^20.11.27",
35
+ "@typescript-eslint/eslint-plugin": "^7.2.0",
36
+ "@typescript-eslint/parser": "^7.2.0",
35
37
  "eslint": "^8.57.0",
36
38
  "eslint-plugin-deprecation": "^2.0.0",
37
39
  "ts-node": "^10.9.2",
38
- "typescript": "^5.3.3"
40
+ "typescript": "^5.4.2"
39
41
  },
40
42
  "license": "MIT",
41
43
  "scripts": {