@13w/miri 1.1.16 → 1.1.18
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 +100 -27
- package/dist/evaluator.js +2 -3
- package/dist/miri.js +4 -4
- package/dist/mongodb.js +0 -1
- package/package.json +16 -16
package/dist/cli.js
CHANGED
|
@@ -1,18 +1,55 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { readFile } from 'node:fs/promises';
|
|
3
|
-
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { lstat, readFile, realpath } from 'node:fs/promises';
|
|
3
|
+
import { createConnection } from 'node:net';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
-
|
|
5
|
+
Error.stackTraceLimit = Infinity;
|
|
6
|
+
import colors from 'colors';
|
|
6
7
|
import { Command } from 'commander';
|
|
7
8
|
import { Table } from 'console-table-printer';
|
|
8
|
-
import colors from 'colors';
|
|
9
|
-
import { createTunnel } from 'tunnel-ssh';
|
|
10
9
|
import SSHConfig from 'ssh-config';
|
|
11
|
-
import
|
|
10
|
+
import { createTunnel } from 'tunnel-ssh';
|
|
12
11
|
import Miri, { IndexStatus, PatchStatus } from './miri.js';
|
|
12
|
+
import connection from './mongodb.js';
|
|
13
13
|
const pkg = await readFile(join(import.meta.dirname, '../package.json'), 'utf-8').then((content) => JSON.parse(content));
|
|
14
14
|
const mirirc = await readFile(join(process.cwd(), '.mirirc'), 'utf-8').then((content) => JSON.parse(content), () => ({}));
|
|
15
|
-
const { SSH_AUTH_SOCK
|
|
15
|
+
const { SSH_AUTH_SOCK } = process.env;
|
|
16
|
+
const fileExists = (filename) => lstat(filename).then(() => true, () => false);
|
|
17
|
+
const isKeyEncrypted = (key) => key.toString('utf-8')
|
|
18
|
+
.split('\n')
|
|
19
|
+
.slice(1, -1)
|
|
20
|
+
.join('')
|
|
21
|
+
.includes('bcrypt');
|
|
22
|
+
const isKeyPublic = (key) => /^(ssh-(rsa|ed25519)|ecdsa-sha2-nistp\d+)\s+[A-Za-z0-9+/=]+/.test(key.toString().trim());
|
|
23
|
+
const listSshAgentKeys = async () => {
|
|
24
|
+
const keys = [];
|
|
25
|
+
if (!SSH_AUTH_SOCK || !(await lstat(SSH_AUTH_SOCK)).isSocket()) {
|
|
26
|
+
return keys;
|
|
27
|
+
}
|
|
28
|
+
const conn = createConnection(SSH_AUTH_SOCK, () => {
|
|
29
|
+
const request = Buffer.from([0, 0, 0, 1, 0x0B]); // SSH_AGENTC_REQUEST_IDENTITIES
|
|
30
|
+
conn.end(request);
|
|
31
|
+
});
|
|
32
|
+
console.log(colors.dim(' reading keys from SSH-Agent...'));
|
|
33
|
+
for await (const chunk of conn) {
|
|
34
|
+
if (chunk.length < 5 || chunk.readUInt8(4) !== 12) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const numKeys = chunk.readUInt32BE(5);
|
|
38
|
+
let offset = 9;
|
|
39
|
+
for (let i = 0; i < numKeys; i += 1) {
|
|
40
|
+
const keyLen = chunk.readUInt32BE(offset);
|
|
41
|
+
offset += 4;
|
|
42
|
+
const key = chunk.slice(offset, offset + keyLen).toString("base64");
|
|
43
|
+
offset += keyLen;
|
|
44
|
+
offset += chunk.readUInt32BE(offset) + 4;
|
|
45
|
+
keys.push(key);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (keys.length) {
|
|
49
|
+
console.log(colors.dim(` ${keys.length} loaded...`));
|
|
50
|
+
}
|
|
51
|
+
return keys;
|
|
52
|
+
};
|
|
16
53
|
const askPassword = async (message = 'Password: ') => {
|
|
17
54
|
process.stdin.resume();
|
|
18
55
|
process.stdin.setEncoding('utf8');
|
|
@@ -41,8 +78,9 @@ const askPassword = async (message = 'Password: ') => {
|
|
|
41
78
|
// Ctrl C
|
|
42
79
|
process.stdin.off('data', readPass);
|
|
43
80
|
reject(new Error('Cancelled'));
|
|
81
|
+
return;
|
|
44
82
|
default:
|
|
45
|
-
// More
|
|
83
|
+
// More password characters
|
|
46
84
|
password += str;
|
|
47
85
|
break;
|
|
48
86
|
}
|
|
@@ -54,7 +92,7 @@ const askPassword = async (message = 'Password: ') => {
|
|
|
54
92
|
const program = new Command();
|
|
55
93
|
program.version(pkg.version);
|
|
56
94
|
program.option('-e --env <environment>', 'Environment name from .mirirc', 'default');
|
|
57
|
-
program.option('-m --migrations <folder>', `Folder with migrations (default: "${
|
|
95
|
+
program.option('-m --migrations <folder>', `Folder with migrations (default: "${join(process.cwd(), 'migrations')}")`);
|
|
58
96
|
program.option('-d --db <mongo-uri>', 'MongoDB Connection URI (default: "mongodb://localhost:27017/test")');
|
|
59
97
|
program.option('--no-direct-connection', 'Disable direct connection', true);
|
|
60
98
|
program.option('--ssh-profile <profile>', 'Connect via SSH using profile');
|
|
@@ -62,9 +100,8 @@ program.option('--ssh-host <host>', 'Connect via SSH proxy Host');
|
|
|
62
100
|
program.option('--ssh-port <port>', 'Connect via SSH proxy Port');
|
|
63
101
|
program.option('--ssh-user <user>', 'Connect via SSH proxy User');
|
|
64
102
|
program.option('--ssh-key <path/to/key>', 'Connect via SSH proxy IdentityKey');
|
|
65
|
-
program.option('--ssh-ask-pass', 'Ask for the private key password');
|
|
66
103
|
let configCache;
|
|
67
|
-
const getConfig = (programOpts) => {
|
|
104
|
+
const getConfig = async (programOpts) => {
|
|
68
105
|
if (configCache) {
|
|
69
106
|
return configCache;
|
|
70
107
|
}
|
|
@@ -72,48 +109,77 @@ const getConfig = (programOpts) => {
|
|
|
72
109
|
const env = envs[programOpts.env ?? 'default'] ?? {};
|
|
73
110
|
const config = Object.assign({}, mirirc, env, programOpts);
|
|
74
111
|
if (config.sshProfile) {
|
|
75
|
-
const sshConfigPath = join(
|
|
76
|
-
const sshConfigContent =
|
|
112
|
+
const sshConfigPath = join(homedir() ?? '', '.ssh/config');
|
|
113
|
+
const sshConfigContent = await readFile(sshConfigPath, 'utf-8');
|
|
77
114
|
console.log(`Reading profile ${config.sshProfile} from SSH config ${sshConfigPath}`);
|
|
78
115
|
const parsed = SSHConfig.parse(sshConfigContent).compute(config.sshProfile);
|
|
79
116
|
config.sshHost = programOpts.sshHost ?? parsed.Hostname;
|
|
80
117
|
config.sshPort = programOpts.sshPort ?? parsed.Port;
|
|
81
118
|
config.sshKey = programOpts.sshKey ?? parsed.IdentityFile?.[0];
|
|
82
119
|
config.sshUser = programOpts.sshUser ?? parsed.User;
|
|
120
|
+
config.forceSshAgent = Boolean(programOpts.sshAgent);
|
|
121
|
+
}
|
|
122
|
+
if (config.sshKey) {
|
|
123
|
+
if (config.sshKey.startsWith('~/')) {
|
|
124
|
+
config.sshKey = config.sshKey.replace(/^~/, homedir());
|
|
125
|
+
}
|
|
126
|
+
config.sshKey = await realpath(config.sshKey);
|
|
127
|
+
if (config.sshKey.endsWith('.pub')) {
|
|
128
|
+
if (await fileExists(config.sshKey)) {
|
|
129
|
+
config.sshPublicKey = config.sshKey;
|
|
130
|
+
}
|
|
131
|
+
const sshKey = config.sshKey.slice(0, -4);
|
|
132
|
+
delete config.sshKey;
|
|
133
|
+
if (await fileExists(sshKey)) {
|
|
134
|
+
config.sshKey = sshKey;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else if (await fileExists(`${config.sshKey}.pub`)) {
|
|
138
|
+
config.sshPublicKey = `${config.sshKey}.pub`;
|
|
139
|
+
}
|
|
83
140
|
}
|
|
84
141
|
return configCache = config;
|
|
85
142
|
};
|
|
86
143
|
const createSSHTunnel = async (opts) => {
|
|
87
|
-
const config = getConfig(opts);
|
|
88
|
-
const passphrase = config.sshKey && config.sshAskPass ? await askPassword() : void 0;
|
|
144
|
+
const config = await getConfig(opts);
|
|
89
145
|
const sshOptions = {
|
|
90
146
|
host: config.sshHost,
|
|
91
147
|
port: Number(config.sshPort ?? 22),
|
|
92
148
|
username: config.sshUser,
|
|
93
149
|
};
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
150
|
+
sshOptions.agent = SSH_AUTH_SOCK;
|
|
151
|
+
if (config.sshPublicKey) {
|
|
152
|
+
const agentKeys = await listSshAgentKeys();
|
|
153
|
+
const publicKeyBody = await readFile(config.sshPublicKey, 'utf-8').catch(() => '');
|
|
154
|
+
if (agentKeys.includes(publicKeyBody.split(' ')[1])) {
|
|
155
|
+
console.log(colors.dim(' Using SSH-Agent'));
|
|
156
|
+
// key already loaded
|
|
157
|
+
}
|
|
158
|
+
else if (config.sshKey) {
|
|
159
|
+
const privateKey = await readFile(config.sshKey).catch(() => void 0);
|
|
160
|
+
if (privateKey && !isKeyPublic(privateKey)) {
|
|
161
|
+
sshOptions.privateKey = privateKey;
|
|
162
|
+
console.log(colors.dim(` Using private key: ${config.sshKey}`));
|
|
163
|
+
if (isKeyEncrypted(privateKey)) {
|
|
164
|
+
sshOptions.passphrase = await askPassword();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
98
167
|
}
|
|
99
168
|
}
|
|
100
|
-
|
|
101
|
-
sshOptions.agent = SSH_AUTH_SOCK;
|
|
102
|
-
}
|
|
103
|
-
console.dir([config, sshOptions]);
|
|
169
|
+
// console.dir([config, sshOptions])
|
|
104
170
|
const dst = new URL(config.db);
|
|
105
171
|
const forwardOptions = {
|
|
106
172
|
dstPort: Number(dst.port ?? 27017),
|
|
107
173
|
dstAddr: dst.hostname,
|
|
108
174
|
};
|
|
109
|
-
const [server] = await createTunnel({ autoClose: true }, {}, sshOptions, forwardOptions);
|
|
175
|
+
const [server] = await createTunnel({ autoClose: true, reconnectOnError: true }, {}, sshOptions, forwardOptions);
|
|
110
176
|
const addressInfo = server.address();
|
|
111
177
|
dst.host = addressInfo.family === 'IPv6' ? `[${String(addressInfo.address)}]` : String(addressInfo.address);
|
|
112
178
|
dst.port = String(addressInfo.port);
|
|
113
179
|
return dst.toString();
|
|
114
180
|
};
|
|
115
181
|
const getMiri = async () => {
|
|
116
|
-
const config = getConfig(program.opts());
|
|
182
|
+
const config = await getConfig(program.opts());
|
|
117
183
|
const db = config.sshHost ? await createSSHTunnel(config) : config.db;
|
|
118
184
|
const dbUri = new URL(db);
|
|
119
185
|
dbUri.searchParams.append('directConnection', String(config.directConnection ?? true));
|
|
@@ -157,7 +223,7 @@ const status = async (remote = false, group, all = false) => {
|
|
|
157
223
|
};
|
|
158
224
|
const syncIndexes = async (miri, coll) => {
|
|
159
225
|
let group = '';
|
|
160
|
-
console.group('Starting
|
|
226
|
+
console.group('Starting synchronization...');
|
|
161
227
|
for await (const { collection, status, name, error } of miri.indexesSync(coll)) {
|
|
162
228
|
if (group !== collection) {
|
|
163
229
|
if (group) {
|
|
@@ -231,11 +297,13 @@ indexesProgram.command('status')
|
|
|
231
297
|
}
|
|
232
298
|
const color = {
|
|
233
299
|
[IndexStatus.New]: colors.green,
|
|
300
|
+
[IndexStatus.Updated]: colors.green,
|
|
234
301
|
[IndexStatus.Applied]: colors.white,
|
|
235
302
|
[IndexStatus.Removed]: colors.red,
|
|
236
303
|
}[detail.status];
|
|
237
304
|
const point = {
|
|
238
305
|
[IndexStatus.New]: colors.green.bold('+ '),
|
|
306
|
+
[IndexStatus.Updated]: colors.green.bold('~ '),
|
|
239
307
|
[IndexStatus.Applied]: colors.cyan('\u00b7 '),
|
|
240
308
|
[IndexStatus.Removed]: colors.red.bold('- '),
|
|
241
309
|
}[detail.status];
|
|
@@ -289,4 +357,9 @@ patchProgram.command('remove')
|
|
|
289
357
|
await miri[Symbol.asyncDispose]();
|
|
290
358
|
});
|
|
291
359
|
program.parse();
|
|
360
|
+
for (const eventName of ['unhandledRejection', 'rejectionHandled', 'uncaughtException']) {
|
|
361
|
+
process.on(eventName, (error) => {
|
|
362
|
+
console.error(colors.red(`${eventName}:\n ${error.name}: ${error.message}`));
|
|
363
|
+
});
|
|
364
|
+
}
|
|
292
365
|
//# sourceMappingURL=cli.js.map
|
package/dist/evaluator.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
1
|
import { EventEmitter } from 'node:events';
|
|
3
|
-
import { createContext, runInContext, SourceTextModule, SyntheticModule } from 'node:vm';
|
|
4
2
|
import { inspect } from 'node:util';
|
|
3
|
+
import { createContext, runInContext, SourceTextModule, SyntheticModule } from 'node:vm';
|
|
5
4
|
import { CliServiceProvider } from '@mongosh/service-provider-server';
|
|
6
5
|
import { ShellInstanceState } from '@mongosh/shell-api';
|
|
7
6
|
import { ShellEvaluator } from '@mongosh/shell-evaluator';
|
|
@@ -46,7 +45,7 @@ export async function evaluateMongo(client, code, filename = '[no file]') {
|
|
|
46
45
|
onLoad() {
|
|
47
46
|
throw new Error('Load isn\'t supported');
|
|
48
47
|
},
|
|
49
|
-
|
|
48
|
+
onExit() {
|
|
50
49
|
throw new Error('Exit isn\'t supported');
|
|
51
50
|
},
|
|
52
51
|
});
|
package/dist/miri.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
1
|
import { createHash } from 'node:crypto';
|
|
3
2
|
import { readdir, readFile, realpath } from 'node:fs/promises';
|
|
4
|
-
import {
|
|
3
|
+
import { basename, extname, resolve } from 'node:path';
|
|
4
|
+
import colors from 'colors';
|
|
5
5
|
import { ObjectId } from 'mongodb';
|
|
6
6
|
import { evaluateJs, evaluateMongo } from './evaluator.js';
|
|
7
|
-
import colors from 'colors';
|
|
8
7
|
export var PatchStatus;
|
|
9
8
|
(function (PatchStatus) {
|
|
10
9
|
PatchStatus[PatchStatus["Ok"] = 0] = "Ok";
|
|
@@ -18,7 +17,8 @@ export var IndexStatus;
|
|
|
18
17
|
(function (IndexStatus) {
|
|
19
18
|
IndexStatus[IndexStatus["New"] = 0] = "New";
|
|
20
19
|
IndexStatus[IndexStatus["Applied"] = 1] = "Applied";
|
|
21
|
-
IndexStatus[IndexStatus["
|
|
20
|
+
IndexStatus[IndexStatus["Updated"] = 2] = "Updated";
|
|
21
|
+
IndexStatus[IndexStatus["Removed"] = 3] = "Removed";
|
|
22
22
|
})(IndexStatus || (IndexStatus = {}));
|
|
23
23
|
const sortPatches = (a, b) => {
|
|
24
24
|
if (a.group === b.group) {
|
package/dist/mongodb.js
CHANGED
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@13w/miri",
|
|
3
3
|
"description": "MongoDB patch manager",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.18",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
|
-
"node": "
|
|
7
|
+
"node": "v22"
|
|
8
8
|
},
|
|
9
9
|
"keywords": [
|
|
10
10
|
"MongoDB",
|
|
@@ -25,29 +25,29 @@
|
|
|
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": "^
|
|
30
|
-
"@mongosh/shell-evaluator": "^
|
|
28
|
+
"@mongosh/service-provider-server": "^2.3.2",
|
|
29
|
+
"@mongosh/shell-api": "^3.8.0",
|
|
30
|
+
"@mongosh/shell-evaluator": "^3.8.0",
|
|
31
31
|
"colors": "^1.4.0",
|
|
32
|
-
"commander": "^
|
|
32
|
+
"commander": "^13.1.0",
|
|
33
33
|
"console-table-printer": "^2.12.1",
|
|
34
|
-
"mongodb": "^6.
|
|
35
|
-
"ssh-config": "^
|
|
36
|
-
"tunnel-ssh": "^5.
|
|
34
|
+
"mongodb": "^6.14.2",
|
|
35
|
+
"ssh-config": "^5.0.3",
|
|
36
|
+
"tunnel-ssh": "^5.2.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@
|
|
40
|
-
"@
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"eslint-plugin-deprecation": "^2.0.0",
|
|
39
|
+
"@eslint/js": "^9.21.0",
|
|
40
|
+
"@types/node": "^22.13.9",
|
|
41
|
+
"eslint": "^9.21.0",
|
|
42
|
+
"mongodb-log-writer": "^2.4.0",
|
|
44
43
|
"ts-node": "^10.9.2",
|
|
45
|
-
"typescript": "^5.
|
|
44
|
+
"typescript": "^5.8.2",
|
|
45
|
+
"typescript-eslint": "^8.26.0"
|
|
46
46
|
},
|
|
47
47
|
"license": "MIT",
|
|
48
48
|
"scripts": {
|
|
49
49
|
"build": "tsc -p tsconfig.json",
|
|
50
|
-
"lint": "eslint src/
|
|
50
|
+
"lint": "eslint --quiet src/",
|
|
51
51
|
"prepublish": "pnpm run build"
|
|
52
52
|
}
|
|
53
53
|
}
|