@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 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 { MIRI_MONGO_URI = 'mongodb://localhost:27017/test', SSH_AUTH_SOCK } = process.env;
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.option('-m --migrations <folder>', 'Folder with migrations', join(process.cwd(), 'migrations'));
15
- program.option('-d --db <mongo-uri>', 'MongoDB Connection URI', MIRI_MONGO_URI);
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: opts.sshHost,
23
- port: Number(opts.sshPort ?? 22),
24
- username: opts.sshUser,
51
+ host: config.sshHost,
52
+ port: Number(config.sshPort ?? 22),
53
+ username: config.sshUser,
25
54
  agent: SSH_AUTH_SOCK,
26
- privateKey: opts.sshKey ? readFileSync(realpathSync(opts.sshKey)) : void 0,
55
+ privateKey: config.sshKey ? readFileSync(realpathSync(config.sshKey)) : void 0,
27
56
  };
28
- const dst = new URL(opts.db);
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 opts = program.opts();
41
- const db = opts.sshHost ? await createSSHTunnel(opts) : opts.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', 'miri+v1');
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: opts.migrations,
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
- .action(async () => {
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 (coll) => {
198
+ .action(async (collection) => {
128
199
  const miri = await getMiri();
129
- let group = '';
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
- console.log('applying');
296
- await this.applyPatchContent({ body: patch.raw, hash: '' }, `${patch.group}/${patch.name}`, false);
297
- await this.#collection.insertOne({
298
- group: patch.group,
299
- name: patch.name,
300
- content: {},
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.10",
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": {