@13w/miri 1.1.10 → 1.1.12
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 +89 -38
- package/dist/miri.js +18 -11
- package/dist/mongodb.js +2 -0
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
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 */
|
|
@@ -5,27 +6,55 @@ import { Command } from 'commander';
|
|
|
5
6
|
import { Table } from 'console-table-printer';
|
|
6
7
|
import colors from 'colors';
|
|
7
8
|
import { createTunnel } from 'tunnel-ssh';
|
|
9
|
+
import SSHConfig from 'ssh-config';
|
|
8
10
|
import connection from './mongodb.js';
|
|
9
11
|
import Miri, { IndexStatus, PatchStatus } from './miri.js';
|
|
10
12
|
import { readFileSync } from 'fs';
|
|
11
13
|
import { realpathSync } from 'node:fs';
|
|
12
|
-
const
|
|
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;
|
|
13
17
|
const program = new Command();
|
|
14
|
-
program.
|
|
15
|
-
program.option('-
|
|
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');
|
|
16
23
|
program.option('--ssh-host <host>', 'Connect via SSH proxy Host');
|
|
17
24
|
program.option('--ssh-port <port>', 'Connect via SSH proxy Port');
|
|
18
25
|
program.option('--ssh-user <user>', 'Connect via SSH proxy User');
|
|
19
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
|
+
};
|
|
20
48
|
const createSSHTunnel = async (opts) => {
|
|
49
|
+
const config = getConfig(opts);
|
|
21
50
|
const sshOptions = {
|
|
22
|
-
host:
|
|
23
|
-
port: Number(
|
|
24
|
-
username:
|
|
51
|
+
host: config.sshHost,
|
|
52
|
+
port: Number(config.sshPort ?? 22),
|
|
53
|
+
username: config.sshUser,
|
|
25
54
|
agent: SSH_AUTH_SOCK,
|
|
26
|
-
privateKey:
|
|
55
|
+
privateKey: config.sshKey ? readFileSync(realpathSync(config.sshKey)) : void 0,
|
|
27
56
|
};
|
|
28
|
-
const dst = new URL(
|
|
57
|
+
const dst = new URL(config.db);
|
|
29
58
|
const forwardOptions = {
|
|
30
59
|
dstPort: Number(dst.port ?? 27017),
|
|
31
60
|
dstAddr: dst.hostname,
|
|
@@ -37,19 +66,23 @@ const createSSHTunnel = async (opts) => {
|
|
|
37
66
|
return dst.toString();
|
|
38
67
|
};
|
|
39
68
|
const getMiri = async () => {
|
|
40
|
-
const
|
|
41
|
-
const db =
|
|
69
|
+
const config = getConfig(program.opts());
|
|
70
|
+
const db = config.sshHost ? await createSSHTunnel(config) : config.db;
|
|
42
71
|
const dbUri = new URL(db);
|
|
43
72
|
dbUri.searchParams.append('directConnection', 'true');
|
|
44
|
-
dbUri.searchParams.append('appName',
|
|
73
|
+
dbUri.searchParams.append('appName', `miri+v${pkg.version}`);
|
|
45
74
|
const client = await connection(dbUri.toString());
|
|
46
75
|
return new Miri(client, {
|
|
47
|
-
localMigrations:
|
|
76
|
+
localMigrations: config.migrations,
|
|
48
77
|
});
|
|
49
78
|
};
|
|
50
|
-
const status = async (remote = false, group) => {
|
|
79
|
+
const status = async (remote = false, group, all = false) => {
|
|
51
80
|
const miri = await getMiri();
|
|
52
81
|
const patches = await miri.stat(remote, group);
|
|
82
|
+
if (!group && all) {
|
|
83
|
+
const initPatches = await miri.stat(remote, 'init');
|
|
84
|
+
patches.unshift(...initPatches);
|
|
85
|
+
}
|
|
53
86
|
await miri[Symbol.asyncDispose]();
|
|
54
87
|
if (!patches.length) {
|
|
55
88
|
return;
|
|
@@ -75,15 +108,53 @@ const status = async (remote = false, group) => {
|
|
|
75
108
|
}
|
|
76
109
|
table.printTable();
|
|
77
110
|
};
|
|
111
|
+
const syncIndexes = async (miri, coll) => {
|
|
112
|
+
let group = '';
|
|
113
|
+
console.group('Starting synchronisation...');
|
|
114
|
+
for await (const { collection, status, name, error } of miri.indexesSync(coll)) {
|
|
115
|
+
if (group !== collection) {
|
|
116
|
+
if (group) {
|
|
117
|
+
console.log('Done');
|
|
118
|
+
console.groupEnd();
|
|
119
|
+
}
|
|
120
|
+
group = collection;
|
|
121
|
+
console.group(`Collection: ${collection}...`);
|
|
122
|
+
}
|
|
123
|
+
if (status === IndexStatus.Applied) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
console[error ? 'group' : 'log'](`${status === IndexStatus.New ? 'Creating' : 'Removing'} index ${name}... ${error ? 'failed' : 'done'}`);
|
|
127
|
+
if (error) {
|
|
128
|
+
console.log(colors.red(error.message));
|
|
129
|
+
console.groupEnd();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
console.groupEnd();
|
|
133
|
+
};
|
|
78
134
|
program.command('status')
|
|
135
|
+
.option('--all', 'show all applied patches')
|
|
79
136
|
.description('Displays list of applied migrations')
|
|
80
|
-
.action(() => status(true));
|
|
137
|
+
.action(({ all }) => status(true, void 0, all));
|
|
138
|
+
program.command('sync')
|
|
139
|
+
.description('Applies all available patches from migrations folder')
|
|
140
|
+
.option('--degraded', 'Re-apply patches on degraded migrations')
|
|
141
|
+
.option('--all', 'Re-apply all patches')
|
|
142
|
+
.action(async (opts) => {
|
|
143
|
+
const miri = await getMiri();
|
|
144
|
+
await miri.init({ force: opts.all });
|
|
145
|
+
await syncIndexes(miri);
|
|
146
|
+
await miri.sync(opts);
|
|
147
|
+
await miri[Symbol.asyncDispose]();
|
|
148
|
+
});
|
|
81
149
|
const initProgram = program.command('init')
|
|
82
150
|
.description('Manage initial scripts');
|
|
83
151
|
initProgram.command('apply')
|
|
84
|
-
.
|
|
152
|
+
.argument('[patch]', 'patch name')
|
|
153
|
+
.option('--no-exec', 'Don\' execute patch, just set as done')
|
|
154
|
+
.option('--force', 'Force apply patch')
|
|
155
|
+
.action(async (opts, patch) => {
|
|
85
156
|
const miri = await getMiri();
|
|
86
|
-
await miri.init();
|
|
157
|
+
await miri.init(opts, patch);
|
|
87
158
|
await miri[Symbol.asyncDispose]();
|
|
88
159
|
});
|
|
89
160
|
initProgram.command('status')
|
|
@@ -124,29 +195,9 @@ indexesProgram.command('status')
|
|
|
124
195
|
});
|
|
125
196
|
const indexSyncProgram = indexesProgram.command('sync');
|
|
126
197
|
indexSyncProgram.argument('[collection]', 'MongoDB Collection name', '')
|
|
127
|
-
.action(async (
|
|
198
|
+
.action(async (collection) => {
|
|
128
199
|
const miri = await getMiri();
|
|
129
|
-
|
|
130
|
-
console.group('Starting synchronisation...');
|
|
131
|
-
for await (const { collection, status, name, error } of miri.indexesSync(coll)) {
|
|
132
|
-
if (group !== collection) {
|
|
133
|
-
if (group) {
|
|
134
|
-
console.log('Done');
|
|
135
|
-
console.groupEnd();
|
|
136
|
-
}
|
|
137
|
-
group = collection;
|
|
138
|
-
console.group(`Collection: ${collection}...`);
|
|
139
|
-
}
|
|
140
|
-
if (status === IndexStatus.Applied) {
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
console[error ? 'group' : 'log'](`${status === IndexStatus.New ? 'Creating' : 'Removing'} index ${name}... ${error ? 'failed' : 'done'}`);
|
|
144
|
-
if (error) {
|
|
145
|
-
console.log(colors.red(error.message));
|
|
146
|
-
console.groupEnd();
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
console.groupEnd();
|
|
200
|
+
await syncIndexes(miri, collection);
|
|
150
201
|
await miri[Symbol.asyncDispose]();
|
|
151
202
|
});
|
|
152
203
|
const patchProgram = program.command('patch')
|
package/dist/miri.js
CHANGED
|
@@ -276,9 +276,9 @@ export default class Migrator {
|
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
}
|
|
279
|
-
async init() {
|
|
280
|
-
const localInits = await this.getLocalPatches(true, 'init');
|
|
281
|
-
const remoteInits = await this.getRemotePatches('init');
|
|
279
|
+
async init({ noExec = false, force = false } = {}, patch) {
|
|
280
|
+
const localInits = await this.getLocalPatches(true, 'init', patch);
|
|
281
|
+
const remoteInits = await this.getRemotePatches('init', patch);
|
|
282
282
|
if (!localInits.length) {
|
|
283
283
|
console.log('Nothing to apply');
|
|
284
284
|
return;
|
|
@@ -287,18 +287,25 @@ export default class Migrator {
|
|
|
287
287
|
for (const patch of localInits) {
|
|
288
288
|
console.group(`Testing ${patch.name}...`);
|
|
289
289
|
const remoteInit = remoteInits.find(({ group, name }) => patch.group === group && patch.name === name);
|
|
290
|
-
if (remoteInit) {
|
|
290
|
+
if (!force && remoteInit) {
|
|
291
291
|
console.log('skip');
|
|
292
292
|
console.groupEnd();
|
|
293
293
|
continue;
|
|
294
294
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
295
|
+
if (force || !noExec) {
|
|
296
|
+
console.log('applying');
|
|
297
|
+
await this.applyPatchContent({ body: patch.raw, hash: '' }, `${patch.group}/${patch.name}`, false);
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
console.log('set as done');
|
|
301
|
+
}
|
|
302
|
+
if (!remoteInit) {
|
|
303
|
+
await this.#collection.insertOne({
|
|
304
|
+
group: patch.group,
|
|
305
|
+
name: patch.name,
|
|
306
|
+
content: {},
|
|
307
|
+
});
|
|
308
|
+
}
|
|
302
309
|
console.groupEnd();
|
|
303
310
|
}
|
|
304
311
|
console.groupEnd();
|
package/dist/mongodb.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
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) {
|
|
6
|
+
console.log(`Connecting MongoDB to ${uri}`);
|
|
5
7
|
client = await MongoClient.connect(uri);
|
|
6
8
|
client?.topology?.socket?.unref();
|
|
7
9
|
}
|
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.12",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "v20"
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"commander": "^12.0.0",
|
|
28
28
|
"console-table-printer": "^2.12.0",
|
|
29
29
|
"mongodb": "^6.5.0",
|
|
30
|
+
"ssh-config": "^4.4.2",
|
|
30
31
|
"tunnel-ssh": "^5.1.2"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|