@13w/miri 1.1.18 → 1.1.20

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
@@ -14,11 +14,7 @@ const pkg = await readFile(join(import.meta.dirname, '../package.json'), 'utf-8'
14
14
  const mirirc = await readFile(join(process.cwd(), '.mirirc'), 'utf-8').then((content) => JSON.parse(content), () => ({}));
15
15
  const { SSH_AUTH_SOCK } = process.env;
16
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');
17
+ const isKeyEncrypted = (key) => key.toString('utf-8').split('\n').slice(1, -1).join('').includes('bcrypt');
22
18
  const isKeyPublic = (key) => /^(ssh-(rsa|ed25519)|ecdsa-sha2-nistp\d+)\s+[A-Za-z0-9+/=]+/.test(key.toString().trim());
23
19
  const listSshAgentKeys = async () => {
24
20
  const keys = [];
@@ -26,10 +22,10 @@ const listSshAgentKeys = async () => {
26
22
  return keys;
27
23
  }
28
24
  const conn = createConnection(SSH_AUTH_SOCK, () => {
29
- const request = Buffer.from([0, 0, 0, 1, 0x0B]); // SSH_AGENTC_REQUEST_IDENTITIES
25
+ const request = Buffer.from([0, 0, 0, 1, 0x0b]); // SSH_AGENTC_REQUEST_IDENTITIES
30
26
  conn.end(request);
31
27
  });
32
- console.log(colors.dim(' reading keys from SSH-Agent...'));
28
+ console.log(colors.gray(' reading keys from SSH-Agent...'));
33
29
  for await (const chunk of conn) {
34
30
  if (chunk.length < 5 || chunk.readUInt8(4) !== 12) {
35
31
  continue;
@@ -39,14 +35,14 @@ const listSshAgentKeys = async () => {
39
35
  for (let i = 0; i < numKeys; i += 1) {
40
36
  const keyLen = chunk.readUInt32BE(offset);
41
37
  offset += 4;
42
- const key = chunk.slice(offset, offset + keyLen).toString("base64");
38
+ const key = chunk.slice(offset, offset + keyLen).toString('base64');
43
39
  offset += keyLen;
44
40
  offset += chunk.readUInt32BE(offset) + 4;
45
41
  keys.push(key);
46
42
  }
47
43
  }
48
44
  if (keys.length) {
49
- console.log(colors.dim(` ${keys.length} loaded...`));
45
+ console.log(colors.gray(` ${keys.length} loaded...`));
50
46
  }
51
47
  return keys;
52
48
  };
@@ -56,7 +52,7 @@ const askPassword = async (message = 'Password: ') => {
56
52
  process.stdin.setRawMode(true);
57
53
  return new Promise((resolve, reject) => {
58
54
  let password = '';
59
- const readPass = function (chunk) {
55
+ const readPass = (chunk) => {
60
56
  const str = chunk.toString();
61
57
  // backspace
62
58
  if (str.charCodeAt(0) === 127) {
@@ -138,7 +134,7 @@ const getConfig = async (programOpts) => {
138
134
  config.sshPublicKey = `${config.sshKey}.pub`;
139
135
  }
140
136
  }
141
- return configCache = config;
137
+ return (configCache = config);
142
138
  };
143
139
  const createSSHTunnel = async (opts) => {
144
140
  const config = await getConfig(opts);
@@ -152,14 +148,14 @@ const createSSHTunnel = async (opts) => {
152
148
  const agentKeys = await listSshAgentKeys();
153
149
  const publicKeyBody = await readFile(config.sshPublicKey, 'utf-8').catch(() => '');
154
150
  if (agentKeys.includes(publicKeyBody.split(' ')[1])) {
155
- console.log(colors.dim(' Using SSH-Agent'));
151
+ console.log(colors.gray(' Using SSH-Agent'));
156
152
  // key already loaded
157
153
  }
158
154
  else if (config.sshKey) {
159
155
  const privateKey = await readFile(config.sshKey).catch(() => void 0);
160
156
  if (privateKey && !isKeyPublic(privateKey)) {
161
157
  sshOptions.privateKey = privateKey;
162
- console.log(colors.dim(` Using private key: ${config.sshKey}`));
158
+ console.log(colors.gray(` Using private key: ${config.sshKey}`));
163
159
  if (isKeyEncrypted(privateKey)) {
164
160
  sshOptions.passphrase = await askPassword();
165
161
  }
@@ -244,11 +240,13 @@ const syncIndexes = async (miri, coll) => {
244
240
  }
245
241
  console.groupEnd();
246
242
  };
247
- program.command('status')
243
+ program
244
+ .command('status')
248
245
  .option('--all', 'show all applied patches')
249
246
  .description('Displays list of applied migrations')
250
247
  .action(({ all }) => status(true, void 0, all));
251
- program.command('sync')
248
+ program
249
+ .command('sync')
252
250
  .description('Applies all available patches from migrations folder')
253
251
  .option('--degraded', 'Re-apply patches on degraded migrations')
254
252
  .option('--all', 'Re-apply all patches')
@@ -259,30 +257,30 @@ program.command('sync')
259
257
  await miri.sync(opts);
260
258
  await miri[Symbol.asyncDispose]();
261
259
  });
262
- const initProgram = program.command('init')
263
- .description('Manage initial scripts');
264
- initProgram.command('apply')
260
+ const initProgram = program.command('init').description('Manage initial scripts');
261
+ initProgram
262
+ .command('apply')
265
263
  .argument('[patch]', 'patch name')
266
- .option('--no-exec', 'Don\' execute patch, just set as done')
264
+ .option('--no-exec', "Don' execute patch, just set as done")
267
265
  .option('--force', 'Force apply patch')
268
266
  .action(async (patch, opts) => {
269
267
  const miri = await getMiri();
270
268
  await miri.init(opts, patch);
271
269
  await miri[Symbol.asyncDispose]();
272
270
  });
273
- initProgram.command('remove')
271
+ initProgram
272
+ .command('remove')
274
273
  .argument('<patch>', 'patch name')
275
- .option('--no-exec', 'Don\' execute patch, just set as done')
274
+ .option('--no-exec', "Don' execute patch, just set as done")
276
275
  .action(async (patch, opts) => {
277
276
  const miri = await getMiri();
278
277
  await miri.remove('init', patch, Boolean(opts?.exec));
279
278
  await miri[Symbol.asyncDispose]();
280
279
  });
281
- initProgram.command('status')
282
- .action(() => status(false, 'init'));
283
- const indexesProgram = program.command('indexes')
284
- .description('Manage indexes');
285
- indexesProgram.command('status')
280
+ initProgram.command('status').action(() => status(false, 'init'));
281
+ const indexesProgram = program.command('indexes').description('Manage indexes');
282
+ indexesProgram
283
+ .command('status')
286
284
  .argument('[collection]', 'MongoDB Collection name')
287
285
  .option('-q --quiet', 'Show only changes', false)
288
286
  .action(async (collection, { quiet }) => {
@@ -317,18 +315,18 @@ indexesProgram.command('status')
317
315
  }
318
316
  });
319
317
  const indexSyncProgram = indexesProgram.command('sync');
320
- indexSyncProgram.argument('[collection]', 'MongoDB Collection name', '')
321
- .action(async (collection) => {
318
+ indexSyncProgram.argument('[collection]', 'MongoDB Collection name', '').action(async (collection) => {
322
319
  const miri = await getMiri();
323
320
  await syncIndexes(miri, collection);
324
321
  await miri[Symbol.asyncDispose]();
325
322
  });
326
- const patchProgram = program.command('patch')
327
- .description('Applies patch to database');
328
- patchProgram.command('diff')
323
+ const patchProgram = program.command('patch').description('Applies patch to database');
324
+ patchProgram
325
+ .command('diff')
329
326
  .description('Displays difference between local and applied migrations')
330
327
  .action(() => status());
331
- patchProgram.command('sync')
328
+ patchProgram
329
+ .command('sync')
332
330
  .description('Applies migrations')
333
331
  .option('--remote', 'Remote only')
334
332
  .option('--degraded', 'Re-apply patches on degraded migrations')
@@ -338,19 +336,21 @@ patchProgram.command('sync')
338
336
  await miri.sync(opts);
339
337
  await miri[Symbol.asyncDispose]();
340
338
  });
341
- patchProgram.command('apply')
339
+ patchProgram
340
+ .command('apply')
342
341
  .argument('<group>', 'group name')
343
342
  .argument('<patch>', 'patch name')
344
- .option('--no-exec', 'Don\' execute patch, just set as done')
343
+ .option('--no-exec', "Don' execute patch, just set as done")
345
344
  .action(async (group, patch, opts) => {
346
345
  const miri = await getMiri();
347
346
  await miri.applySingle(group, patch, Boolean(opts.exec));
348
347
  await miri[Symbol.asyncDispose]();
349
348
  });
350
- patchProgram.command('remove')
349
+ patchProgram
350
+ .command('remove')
351
351
  .argument('<group>', 'group name')
352
352
  .argument('<patch>', 'patch name')
353
- .option('--no-exec', 'Don\' execute patch, just set as done')
353
+ .option('--no-exec', "Don' execute patch, just set as done")
354
354
  .action(async (group, patch, opts) => {
355
355
  const miri = await getMiri();
356
356
  await miri.remove(group, patch, Boolean(opts.exec));
package/dist/evaluator.js CHANGED
@@ -10,9 +10,7 @@ const print = (values, type) => {
10
10
  if (type === 'print') {
11
11
  return String(object);
12
12
  }
13
- else {
14
- return inspect(object, { colors: false, customInspect: true, depth: 22 });
15
- }
13
+ return inspect(object, { colors: false, customInspect: true, depth: 22 });
16
14
  };
17
15
  for (const value of values) {
18
16
  const printable = value.printable ?? value.rawValue;
@@ -33,24 +31,34 @@ export async function evaluateMongo(client, code, filename = '[no file]') {
33
31
  productName: 'MIRI: Migration manager',
34
32
  productDocsLink: 'https://example.com/',
35
33
  });
36
- const instanceState = new ShellInstanceState(cliServiceProvider);
34
+ // Add compatibility layer for missing clientBulkWrite method
35
+ // Properly extend the service provider to preserve all prototype methods
36
+ const serviceProviderWithBulkWrite = Object.create(Object.getPrototypeOf(cliServiceProvider));
37
+ Object.assign(serviceProviderWithBulkWrite, cliServiceProvider);
38
+ serviceProviderWithBulkWrite.clientBulkWrite = (operations, options) => {
39
+ // Fallback implementation - delegate to client if available
40
+ if ('bulkWrite' in client && typeof client.bulkWrite === 'function') {
41
+ return client.bulkWrite(operations, options);
42
+ }
43
+ throw new Error('clientBulkWrite not supported in this MongoDB version');
44
+ };
45
+ const instanceState = new ShellInstanceState(serviceProviderWithBulkWrite);
37
46
  instanceState.setCtx(context);
38
47
  instanceState.setEvaluationListener({
39
48
  onPrint(values, type) {
40
49
  return print(values, type);
41
50
  },
42
51
  onPrompt() {
43
- throw new Error('Prompt isn\'t supported');
52
+ throw new Error("Prompt isn't supported");
44
53
  },
45
54
  onLoad() {
46
- throw new Error('Load isn\'t supported');
55
+ throw new Error("Load isn't supported");
47
56
  },
48
57
  onExit() {
49
- throw new Error('Exit isn\'t supported');
58
+ throw new Error("Exit isn't supported");
50
59
  },
51
60
  });
52
- const output = await new ShellEvaluator(instanceState)
53
- .customEval(runInContext, `${code};`, context, filename);
61
+ const output = await new ShellEvaluator(instanceState).customEval(runInContext, `${code};`, context, filename);
54
62
  return output.rawValue;
55
63
  }
56
64
  export async function evaluateJs(code, identifier = '[no file]') {
@@ -63,8 +71,7 @@ export async function evaluateJs(code, identifier = '[no file]') {
63
71
  }, { context: referencingModule.context });
64
72
  });
65
73
  });
66
- await module.evaluate()
67
- .catch((error) => {
74
+ await module.evaluate().catch((error) => {
68
75
  error.message += ` # at file ${identifier}`;
69
76
  throw error;
70
77
  });
package/dist/miri.js CHANGED
@@ -53,8 +53,8 @@ export default class Migrator {
53
53
  if (localPatch.content.up?.hash !== remotePatch.content.up?.hash) {
54
54
  localPatch.status = PatchStatus.Changed;
55
55
  }
56
- else if (localPatch.content.test?.hash !== remotePatch.content.test?.hash
57
- || localPatch.content.down?.hash !== remotePatch.content.down?.hash) {
56
+ else if (localPatch.content.test?.hash !== remotePatch.content.test?.hash ||
57
+ localPatch.content.down?.hash !== remotePatch.content.down?.hash) {
58
58
  localPatch.status = PatchStatus.Updated;
59
59
  }
60
60
  if (localPatch.status !== PatchStatus.Ok) {
@@ -81,10 +81,9 @@ export default class Migrator {
81
81
  return -1;
82
82
  }
83
83
  const code = Buffer.from(content.body, 'base64').toString();
84
- return evaluateMongo(this.client, wrap ? `(async ${code})();` : code, filename)
85
- .catch((error) => {
84
+ return (evaluateMongo(this.client, wrap ? `(async ${code})();` : code, filename).catch((error) => {
86
85
  throw error;
87
- }) ?? 0;
86
+ }) ?? 0);
88
87
  }
89
88
  async sync({ remote = false, all = false, degraded = false } = {}) {
90
89
  const patches = remote ? await this.getRemotePatches() : await this.diff();
@@ -147,9 +146,12 @@ export default class Migrator {
147
146
  if (!['.js', '.json'].includes(extension)) {
148
147
  continue;
149
148
  }
149
+ // Construct full file path manually for newer Node.js types compatibility
150
+ const filePath = file.parentPath || resolve(migrationsDir, file.name);
151
+ const fullFilePath = resolve(filePath, file.name);
150
152
  const patchObject = {
151
153
  _id: new ObjectId(),
152
- group: file.path.substring(migrationsDir.length + 1),
154
+ group: filePath.substring(migrationsDir.length + 1),
153
155
  name: file.name,
154
156
  title: basename(file.name, extension),
155
157
  content: {},
@@ -161,13 +163,12 @@ export default class Migrator {
161
163
  continue;
162
164
  }
163
165
  patches.push(patchObject);
164
- const patchContent = await readFile(resolve(process.cwd(), file.path, file.name));
166
+ const patchContent = await readFile(fullFilePath);
165
167
  if (raw) {
166
168
  patchObject.raw = patchContent.toString('base64');
167
169
  continue;
168
170
  }
169
- const patchExports = await evaluateJs(patchContent.toString())
170
- .catch(() => ({}));
171
+ const patchExports = await evaluateJs(patchContent.toString()).catch(() => ({}));
171
172
  for (const key of ['test', 'up', 'down']) {
172
173
  const func = patchExports[key];
173
174
  if (typeof func !== 'function') {
@@ -192,7 +193,8 @@ export default class Migrator {
192
193
  if (name) {
193
194
  filter.name = name;
194
195
  }
195
- const patches = await this.#collection.find(filter, {
196
+ const patches = await this.#collection
197
+ .find(filter, {
196
198
  projection: {
197
199
  _id: 1,
198
200
  group: 1,
@@ -212,7 +214,8 @@ export default class Migrator {
212
214
  },
213
215
  },
214
216
  },
215
- }).toArray();
217
+ })
218
+ .toArray();
216
219
  patches.sort(sortPatches);
217
220
  return patches;
218
221
  }
@@ -225,7 +228,9 @@ export default class Migrator {
225
228
  structure[patch.title] = {};
226
229
  const collection = structure[patch.title];
227
230
  // console.log(`Reading indexes from ${patch.title}...`)
228
- const remoteIndexes = await db.collection(patch.title).indexes({ full: true })
231
+ const remoteIndexes = await db
232
+ .collection(patch.title)
233
+ .indexes({ full: true })
229
234
  .catch(() => []);
230
235
  // console.dir([patch.title, remoteIndexes], { colors: true, customInspect: true, depth: 22 })
231
236
  for (const index of indexes) {
@@ -233,7 +238,7 @@ export default class Migrator {
233
238
  if (typeof index === 'string') {
234
239
  continue;
235
240
  }
236
- const [key, options] = (Array.isArray(index) ? index : [index, {}]);
241
+ const [key, options] = Array.isArray(index) ? index : [index, {}];
237
242
  const name = Object.entries(key).reduce((result, [key, value]) => `${result ? `${result}_` : ''}${key}_${value}`, '');
238
243
  const remoteIndex = remoteIndexes.find(({ name: indexName }) => indexName === name);
239
244
  collection[name] = { key, options, status: remoteIndex ? IndexStatus.Applied : IndexStatus.New };
@@ -259,14 +264,16 @@ export default class Migrator {
259
264
  const coll = db.collection(collection);
260
265
  for (const [name, { key, status, options }] of Object.entries(indexes)) {
261
266
  if (status === IndexStatus.Removed) {
262
- const error = await coll.dropIndex(name)
267
+ const error = await coll
268
+ .dropIndex(name)
263
269
  .then(() => { })
264
270
  .catch((error) => error);
265
271
  yield { collection, name, error, status };
266
272
  continue;
267
273
  }
268
274
  if (status === IndexStatus.New) {
269
- const error = await coll.createIndex(key, options)
275
+ const error = await coll
276
+ .createIndex(key, options)
270
277
  .then(() => { })
271
278
  .catch((error) => error);
272
279
  yield { collection, name, error, status };
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.18",
4
+ "version": "1.1.20",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": "v22"
@@ -26,23 +26,23 @@
26
26
  "types": "types/miri.d.ts",
27
27
  "dependencies": {
28
28
  "@mongosh/service-provider-server": "^2.3.2",
29
- "@mongosh/shell-api": "^3.8.0",
30
- "@mongosh/shell-evaluator": "^3.8.0",
29
+ "@mongosh/shell-api": "^3.20.0",
30
+ "@mongosh/shell-evaluator": "^3.20.0",
31
31
  "colors": "^1.4.0",
32
- "commander": "^13.1.0",
33
- "console-table-printer": "^2.12.1",
34
- "mongodb": "^6.14.2",
32
+ "commander": "^14.0.0",
33
+ "console-table-printer": "^2.14.6",
34
+ "mongodb": "^6.19.0",
35
35
  "ssh-config": "^5.0.3",
36
36
  "tunnel-ssh": "^5.2.0"
37
37
  },
38
38
  "devDependencies": {
39
- "@eslint/js": "^9.21.0",
40
- "@types/node": "^22.13.9",
41
- "eslint": "^9.21.0",
42
- "mongodb-log-writer": "^2.4.0",
39
+ "@eslint/js": "^9.34.0",
40
+ "@types/node": "^24.3.0",
41
+ "eslint": "^9.34.0",
42
+ "mongodb-log-writer": "^2.4.1",
43
43
  "ts-node": "^10.9.2",
44
- "typescript": "^5.8.2",
45
- "typescript-eslint": "^8.26.0"
44
+ "typescript": "^5.9.2",
45
+ "typescript-eslint": "^8.42.0"
46
46
  },
47
47
  "license": "MIT",
48
48
  "scripts": {