mysql-memory-server 1.8.1 → 1.9.0

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.
@@ -37,7 +37,10 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
37
37
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
38
38
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
39
39
  };
40
- var _Executor_instances, _Executor_executeFile, _Executor_killProcess, _Executor_deleteDatabaseDirectory, _Executor_returnBinaryPathToDelete, _Executor_startMySQLProcess, _Executor_setupDataDirectories;
40
+ var __importDefault = (this && this.__importDefault) || function (mod) {
41
+ return (mod && mod.__esModule) ? mod : { "default": mod };
42
+ };
43
+ var _Executor_instances, _Executor_executeFile, _Executor_killProcess, _Executor_returnBinaryPathToDelete, _Executor_startMySQLProcess, _Executor_setupDataDirectories;
41
44
  Object.defineProperty(exports, "__esModule", { value: true });
42
45
  const child_process_1 = require("child_process");
43
46
  const semver_1 = require("semver");
@@ -49,6 +52,8 @@ const path_1 = require("path");
49
52
  const FileLock_1 = require("./FileLock");
50
53
  const signal_exit_1 = require("signal-exit");
51
54
  const crypto_1 = require("crypto");
55
+ const constants_1 = require("../constants");
56
+ const LinuxOSRelease_1 = __importDefault(require("./LinuxOSRelease"));
52
57
  class Executor {
53
58
  constructor(logger) {
54
59
  _Executor_instances.add(this);
@@ -118,13 +123,13 @@ class Executor {
118
123
  this.version = installedMySQLBinary.version;
119
124
  this.versionInstalledOnSystem = installedMySQLBinary.installedOnSystem;
120
125
  this.removeExitHandler = (0, signal_exit_1.onExit)(() => {
121
- if (options._DO_NOT_USE_cli) {
126
+ if ((0, constants_1.getInternalEnvVariable)('cli') === 'true') {
122
127
  console.log('\nShutting down the ephemeral MySQL database and cleaning all related files...');
123
128
  }
124
129
  this.DBDestroySignal.abort();
125
- if (options._DO_NOT_USE_deleteDBAfterStopped) {
130
+ if ((0, constants_1.getInternalEnvVariable)('deleteDBAfterStopped') === 'true') {
126
131
  try {
127
- fs.rmSync(options._DO_NOT_USE_dbPath, { recursive: true, maxRetries: 50, force: true });
132
+ fs.rmSync(this.databasePath, { recursive: true, maxRetries: 50, force: true });
128
133
  }
129
134
  catch (e) {
130
135
  this.logger.error('An error occurred while deleting database directory path:', e);
@@ -139,21 +144,22 @@ class Executor {
139
144
  this.logger.error('An error occurred while deleting database binary:', e);
140
145
  }
141
146
  }
142
- if (options._DO_NOT_USE_cli) {
147
+ if ((0, constants_1.getInternalEnvVariable)('cli') === 'true') {
143
148
  console.log('Shutdown and cleanup is complete.');
144
149
  }
145
150
  });
146
151
  let retries = 0;
147
- const datadir = (0, path_1.normalize)(`${options._DO_NOT_USE_dbPath}/data`);
152
+ this.databasePath = (0, path_1.normalize)(`${(0, constants_1.getInternalEnvVariable)('databaseDirectoryPath')}/${(0, crypto_1.randomUUID)().replaceAll("-", '')}`);
153
+ const datadir = (0, path_1.normalize)(`${this.databasePath}/data`);
148
154
  do {
149
- await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_setupDataDirectories).call(this, options, installedMySQLBinary.path, datadir, true);
155
+ await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_setupDataDirectories).call(this, options, installedMySQLBinary, datadir, true);
150
156
  this.logger.log('Setting up directories was successful');
151
157
  const port = options.port || (0, Port_1.GenerateRandomPort)();
152
158
  const mySQLXPort = options.xPort || (0, Port_1.GenerateRandomPort)();
153
159
  this.logger.log('Using port:', port, 'and MySQLX port:', mySQLXPort, 'on retry:', retries);
154
160
  try {
155
161
  this.logger.log('Starting MySQL process');
156
- const resolved = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_startMySQLProcess).call(this, options, port, mySQLXPort, datadir, options._DO_NOT_USE_dbPath, installedMySQLBinary.path);
162
+ const resolved = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_startMySQLProcess).call(this, options, port, mySQLXPort, datadir, this.databasePath, installedMySQLBinary.path);
157
163
  this.logger.log('Starting process was successful');
158
164
  return resolved;
159
165
  }
@@ -195,28 +201,6 @@ _Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_
195
201
  killed = process.kill();
196
202
  }
197
203
  return killed;
198
- }, _Executor_deleteDatabaseDirectory = async function _Executor_deleteDatabaseDirectory(path) {
199
- let retries = 0;
200
- //Maximum wait of 10 seconds | 500ms * 20 retries = 10,000ms = 10 seconds
201
- const waitTime = 500;
202
- const maxRetries = 20;
203
- //Since the database processes are killed instantly (SIGKILL) sometimes the database file handles may still be open
204
- //This would cause an EBUSY error. Retrying the deletions for 10 seconds should give the OS enough time to close
205
- //the file handles.
206
- while (retries <= maxRetries) {
207
- try {
208
- await fsPromises.rm(path, { recursive: true, force: true });
209
- return;
210
- }
211
- catch (e) {
212
- if (retries === maxRetries) {
213
- throw e;
214
- }
215
- await new Promise(resolve => setTimeout(resolve, waitTime));
216
- retries++;
217
- this.logger.log('DB data directory deletion failed. Now on retry', retries);
218
- }
219
- }
220
204
  }, _Executor_returnBinaryPathToDelete = function _Executor_returnBinaryPathToDelete(binaryFilepath, options) {
221
205
  if (binaryFilepath.includes(os.tmpdir()) && !options.downloadBinaryOnce) {
222
206
  const splitPath = binaryFilepath.split(os.platform() === 'win32' ? '\\' : '/');
@@ -234,12 +218,51 @@ _Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_
234
218
  await fsPromises.rm(logFile, { force: true });
235
219
  const socket = os.platform() === 'win32' ? `MySQL-${(0, crypto_1.randomUUID)()}` : `${dbPath}/m.sock`;
236
220
  const xSocket = os.platform() === 'win32' ? `MySQLX-${(0, crypto_1.randomUUID)()}` : `${dbPath}/x.sock`;
237
- const process = (0, child_process_1.spawn)(binaryFilepath, ['--no-defaults', `--port=${port}`, `--datadir=${datadir}`, `--mysqlx-port=${mySQLXPort}`, `--mysqlx-socket=${xSocket}`, `--socket=${socket}`, `--general-log-file=${logFile}`, '--general-log=1', `--init-file=${dbPath}/init.sql`, '--bind-address=127.0.0.1', '--innodb-doublewrite=OFF', '--mysqlx=FORCE', `--log-error=${errorLogFile}`, `--user=${os.userInfo().username}`], { signal: this.DBDestroySignal.signal, killSignal: 'SIGKILL' });
221
+ const mysqlArguments = [
222
+ '--no-defaults',
223
+ '--mysqlx=FORCE',
224
+ `--port=${port}`,
225
+ `--datadir=${datadir}`,
226
+ `--mysqlx-port=${mySQLXPort}`,
227
+ `--mysqlx-socket=${xSocket}`,
228
+ `--socket=${socket}`,
229
+ `--general-log-file=${logFile}`,
230
+ '--general-log=1',
231
+ `--init-file=${dbPath}/init.sql`,
232
+ '--bind-address=127.0.0.1',
233
+ '--innodb-doublewrite=OFF',
234
+ `--log-error=${errorLogFile}`,
235
+ `--user=${os.userInfo().username}`
236
+ ];
237
+ //<8.0.11 does not have MySQL X turned on by default so we will be installing the X Plugin in this if statement.
238
+ //MySQL 5.7.12 introduced the X plugin, but according to https://dev.mysql.com/doc/refman/5.7/en/document-store-setting-up.html, the database needs to be initialised with version 5.7.19.
239
+ //If the MySQL version is >=5.7.19 & <8.0.11 then install the X Plugin
240
+ if ((0, semver_1.lt)(this.version, '8.0.11') && (0, semver_1.gte)(this.version, '5.7.19')) {
241
+ const pluginExtension = os.platform() === 'win32' ? 'dll' : 'so';
242
+ let pluginPath;
243
+ const firstPath = (0, path_1.resolve)(`${binaryFilepath}/../../lib/plugin`);
244
+ const secondPath = '/usr/lib/mysql/plugin';
245
+ if (fs.existsSync(`${firstPath}/mysqlx.${pluginExtension}`)) {
246
+ pluginPath = firstPath;
247
+ }
248
+ else if (os.platform() === 'linux' && fs.existsSync(`${secondPath}/mysqlx.so`)) {
249
+ pluginPath = secondPath;
250
+ }
251
+ else {
252
+ throw 'Could not install MySQL X as the path to the plugin cannot be found.';
253
+ }
254
+ mysqlArguments.splice(1, 0, `--plugin-dir=${pluginPath}`, `--early-plugin-load=mysqlx=mysqlx.${pluginExtension};`);
255
+ }
256
+ const process = (0, child_process_1.spawn)(binaryFilepath, mysqlArguments, { signal: this.DBDestroySignal.signal, killSignal: 'SIGKILL' });
238
257
  //resolveFunction is the function that will be called to resolve the promise that stops the database.
239
258
  //If resolveFunction is not undefined, the database has received a kill signal and data cleanup procedures should run.
240
259
  //Once ran, resolveFunction will be called.
241
260
  let resolveFunction;
242
261
  process.on('close', async (code, signal) => {
262
+ if (signal) {
263
+ this.logger.log('Exiting because of aborted signal.');
264
+ return;
265
+ }
243
266
  let errorLog;
244
267
  try {
245
268
  errorLog = await fsPromises.readFile(errorLogFile, { encoding: 'utf-8' });
@@ -247,13 +270,19 @@ _Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_
247
270
  catch (e) {
248
271
  errorLog = `ERROR WHILE READING LOG: ${e}`;
249
272
  }
250
- const portIssue = errorLog.includes("Do you already have another mysqld server running");
251
- const xPortIssue = errorLog.includes("X Plugin can't bind to it");
252
- this.logger.log('Exiting because of a port issue:', portIssue, '. MySQL X Plugin failed to bind:', xPortIssue);
253
- if (portIssue || xPortIssue) {
273
+ if (!this.killedFromPortIssue) {
274
+ //A check is done after the error log file says that the server is ready for connections.
275
+ //When MySQL X cannot bind to a port, the server still says it is ready for connections so killedFromPortIssue gets set to true
276
+ //This if statement will be ran if the server did not encounter a port issue or if the MySQL server could not bind to its port
277
+ this.killedFromPortIssue = errorLog.includes("Do you already have another mysqld server running");
278
+ this.logger.log('Did a port issue occur between server start and server close:', this.killedFromPortIssue);
279
+ }
280
+ if (this.killedFromPortIssue) {
254
281
  this.logger.log('Error log when exiting for port in use error:', errorLog);
255
282
  try {
256
- await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_deleteDatabaseDirectory).call(this, options._DO_NOT_USE_dbPath);
283
+ this.logger.log('Deleting database path after port issue...');
284
+ await fsPromises.rm(dbPath, { recursive: true, force: true, maxRetries: 50, retryDelay: 100 });
285
+ this.logger.log('Successfully deleted database after port issue.');
257
286
  }
258
287
  catch (e) {
259
288
  this.logger.error(e);
@@ -262,8 +291,8 @@ _Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_
262
291
  return reject('Port is already in use');
263
292
  }
264
293
  try {
265
- if (options._DO_NOT_USE_deleteDBAfterStopped) {
266
- await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_deleteDatabaseDirectory).call(this, dbPath);
294
+ if ((0, constants_1.getInternalEnvVariable)('deleteDBAfterStopped') === 'true') {
295
+ await fsPromises.rm(dbPath, { recursive: true, force: true, maxRetries: 50, retryDelay: 100 });
267
296
  }
268
297
  }
269
298
  catch (e) {
@@ -310,18 +339,17 @@ _Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_
310
339
  if (curr.dev !== 0) {
311
340
  //File exists
312
341
  const file = await fsPromises.readFile(errorLogFile, { encoding: 'utf8' });
313
- if (file.includes("X Plugin can't bind to it")) {
314
- //As stated in the MySQL X Plugin documentation at https://dev.mysql.com/doc/refman/8.4/en/x-plugin-options-system-variables.html#sysvar_mysqlx_bind_address
315
- //when the MySQL X Plugin fails to bind to an address, it does not prevent the MySQL server startup because MySQL X is not a mandatory plugin.
316
- //It doesn't seem like there is a way to prevent server startup when that happens. The workaround to that is to shutdown the MySQL server ourselves when the X plugin
317
- //cannot bind to an address. If there is a way to prevent server startup when binding fails, this workaround can be removed.
318
- const killed = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_killProcess).call(this, process);
319
- if (!killed) {
320
- reject('Failed to kill MySQL process to retry listening on a free port.');
321
- }
322
- }
323
- else if (file.includes('ready for connections. Version:')) {
342
+ if (file.includes(': ready for connections') || file.includes('Server starts handling incoming connections')) {
324
343
  fs.unwatchFile(errorLogFile);
344
+ this.killedFromPortIssue = file.includes("Do you already have another mysqld server running");
345
+ this.logger.log('Did a port issue occur after watching errorLogFile:', this.killedFromPortIssue);
346
+ if (this.killedFromPortIssue) {
347
+ const killed = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_killProcess).call(this, process);
348
+ if (!killed) {
349
+ return reject('Failed to kill process after port error.');
350
+ }
351
+ return; //Promise rejection will be handled in the process.on('close') section because this.killedFromPortIssue is being set to true
352
+ }
325
353
  resolve({
326
354
  port,
327
355
  xPort: mySQLXPort,
@@ -339,7 +367,7 @@ _Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_
339
367
  this.removeExitHandler();
340
368
  const killed = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_killProcess).call(this, process);
341
369
  if (!killed) {
342
- reject();
370
+ reject('Failed to kill process.');
343
371
  }
344
372
  });
345
373
  }
@@ -348,59 +376,53 @@ _Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_
348
376
  }
349
377
  });
350
378
  });
351
- }, _Executor_setupDataDirectories = async function _Executor_setupDataDirectories(options, binaryFilepath, datadir, retry) {
379
+ }, _Executor_setupDataDirectories = async function _Executor_setupDataDirectories(options, binary, datadir, retry) {
380
+ const binaryFilepath = binary.path;
352
381
  this.logger.log('Created data directory for database at:', datadir);
353
382
  await fsPromises.mkdir(datadir, { recursive: true });
354
- let stderr;
355
- if (binaryFilepath === 'mysqld') {
356
- const { error, stderr: output } = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_executeFile).call(this, 'mysqld', ['--no-defaults', `--datadir=${datadir}`, '--initialize-insecure']);
357
- stderr = output;
358
- if (error) {
359
- this.logger.error('An error occurred while initializing database with system-installed MySQL:', error);
360
- throw 'An error occurred while initializing database with system-installed MySQL. Please check the console for more information.';
361
- }
362
- }
363
- else {
364
- let result;
365
- try {
366
- result = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_executeFile).call(this, `${binaryFilepath}`, [`--no-defaults`, `--datadir=${datadir}`, `--initialize-insecure`]);
367
- }
368
- catch (e) {
369
- this.logger.error('Error occurred from executeFile:', e);
370
- throw e;
371
- }
372
- stderr = result === null || result === void 0 ? void 0 : result.stderr;
383
+ const { error, stderr } = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_executeFile).call(this, binaryFilepath, ['--no-defaults', `--datadir=${datadir}`, '--initialize-insecure']);
384
+ if (binary.installedOnSystem && error) {
385
+ this.logger.error('An error occurred while initializing database with the system-installed MySQL binary:', error);
386
+ throw `An error occurred while initializing the MySQL database: ${error}`;
373
387
  }
374
- if (stderr && !stderr.includes('InnoDB initialization has ended')) {
388
+ if (stderr && !stderr.includes('is created with an empty password')) {
375
389
  if (process.platform === 'win32' && stderr.includes('Command failed')) {
376
390
  this.logger.error(stderr);
377
391
  throw 'The mysqld command failed to run. A possible cause is that the Microsoft Visual C++ Redistributable Package is not installed. MySQL 5.7.40 and newer requires Microsoft Visual C++ Redistributable Package 2019 to be installed. Check the MySQL docs for Microsoft Visual C++ requirements for other MySQL versions. If you are sure you have this installed, check the error message in the console for more details.';
378
392
  }
379
393
  if (process.platform === 'linux' && stderr.includes('libaio.so')) {
380
- if (binaryFilepath === 'mysqld') {
381
- throw 'libaio could not be found while running system-installed MySQL. libaio must be installed on this system for MySQL to run. To learn more, please check out https://dev.mysql.com/doc/refman/en/binary-installation.html';
382
- }
383
394
  if (retry === false) {
384
395
  this.logger.error('An error occurred while initializing database:', stderr);
385
396
  throw 'Tried to copy libaio into lib folder and MySQL is still failing to initialize. Please check the console for more information.';
386
397
  }
387
- if (binaryFilepath.slice(-16) === 'mysql/bin/mysqld') {
398
+ if (binary.installedOnSystem) {
399
+ throw 'libaio could not be found while running system-installed MySQL. libaio must be installed on this system for MySQL to run. To learn more, please check out https://dev.mysql.com/doc/refman/en/binary-installation.html';
400
+ }
401
+ // If the below code is running, the version of MySQL that is trying to be executed was downloaded from the CDN by this package and libaio has not yet been attempted to be copied
402
+ if (LinuxOSRelease_1.default.NAME === 'Ubuntu' && LinuxOSRelease_1.default.VERSION_ID >= '24.04') {
388
403
  const { error: lderror, stdout, stderr: ldstderr } = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_executeFile).call(this, 'ldconfig', ['-p']);
389
404
  if (lderror || ldstderr) {
390
405
  this.logger.error('The following libaio error occurred:', stderr);
391
406
  this.logger.error('After the libaio error, an ldconfig error occurred:', lderror || ldstderr);
392
- throw 'The ldconfig command failed to run. This command was ran to find libaio because libaio could not be found on the system. libaio is needed for MySQL to run. Do you have ldconfig and libaio installed? Learn more about libaio at Learn more at https://dev.mysql.com/doc/refman/en/binary-installation.html';
407
+ throw 'The ldconfig command failed to run. This command was ran to find libaio1t64 because libaio1t64 could not be found on the system. libaio1t64 is needed for MySQL to run. Do you have ldconfig and libaio1t64 installed? Learn more at https://dev.mysql.com/doc/refman/en/binary-installation.html';
393
408
  }
394
409
  const libaioFound = stdout.split('\n').filter(lib => lib.includes('libaio.so.1t64'));
395
410
  if (!libaioFound.length) {
396
411
  this.logger.error('Error from launching MySQL:', stderr);
397
- throw 'An error occurred while launching MySQL. The most likely cause is that libaio1 and libaio1t64 could not be found. Either libaio1 or libaio1t64 must be installed on this system for MySQL to run. To learn more, please check out https://dev.mysql.com/doc/refman/en/binary-installation.html. Check error in console for more information.';
412
+ this.logger.error('Could not find libaio1t64 in this list:', libaioFound);
413
+ throw 'An error occurred while launching MySQL because libaio1t64 is not installed on your system. Please install libaio1t64 and then use mysql-memory-server again. To learn more, please check out https://dev.mysql.com/doc/refman/en/binary-installation.html. Check error in console for more information.';
398
414
  }
399
415
  const libaioEntry = libaioFound[0];
400
416
  const libaioPathIndex = libaioEntry.indexOf('=>');
401
417
  const libaioSymlinkPath = libaioEntry.slice(libaioPathIndex + 3);
402
418
  const libaioPath = await fsPromises.realpath(libaioSymlinkPath);
403
- const copyPath = (0, path_1.resolve)(`${binaryFilepath}/../../lib/private/libaio.so.1`);
419
+ let copyPath;
420
+ if ((0, semver_1.lt)(this.version, '8.0.18')) {
421
+ copyPath = (0, path_1.resolve)(`${binaryFilepath}/../../bin/libaio.so.1`);
422
+ }
423
+ else {
424
+ copyPath = (0, path_1.resolve)(`${binaryFilepath}/../../lib/private/libaio.so.1`);
425
+ }
404
426
  let lockRelease;
405
427
  while (true) {
406
428
  try {
@@ -454,30 +476,28 @@ _Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_
454
476
  if (copyError) {
455
477
  throw 'An error occurred while copying libaio1t64 to the MySQL lib folder. Please check the console for more details.';
456
478
  }
457
- //Retry setting up directory now that libaio has been copied
458
- this.logger.log('Retrying directory setup');
459
- await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_deleteDatabaseDirectory).call(this, datadir);
460
- await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_setupDataDirectories).call(this, options, binaryFilepath, datadir, false);
461
- return;
462
479
  }
463
480
  }
481
+ //Retry setting up directory now that libaio has been copied
482
+ this.logger.log('Retrying directory setup');
483
+ await fsPromises.rm(datadir, { recursive: true, force: true, maxRetries: 50, retryDelay: 100 });
484
+ await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_setupDataDirectories).call(this, options, binary, datadir, false);
485
+ return;
464
486
  }
465
- else {
466
- throw 'Cannot recognize file structure for the MySQL binary folder. This was caused by not being able to find libaio. Try installing libaio. Learn more at https://dev.mysql.com/doc/refman/en/binary-installation.html';
467
- }
487
+ throw 'You do not have libaio1 installed. Please install the libaio1 package for the downloaded MySQL binary to run. Learn more here: https://dev.mysql.com/doc/refman/en/binary-installation.html';
468
488
  }
469
489
  throw stderr;
470
490
  }
471
491
  this.logger.log('Creating init text');
472
492
  let initText = `CREATE DATABASE ${options.dbName};`;
473
493
  if (options.username !== 'root') {
474
- initText += `\nRENAME USER 'root'@'localhost' TO '${options.username}'@'localhost';`;
494
+ initText += `\nCREATE USER '${options.username}'@'localhost';\nGRANT ALL ON *.* TO '${options.username}'@'localhost' WITH GRANT OPTION;`;
475
495
  }
476
496
  if (options.initSQLString.length > 0) {
477
497
  initText += `\n${options.initSQLString}`;
478
498
  }
479
499
  this.logger.log('Writing init file');
480
- await fsPromises.writeFile(`${options._DO_NOT_USE_dbPath}/init.sql`, initText, { encoding: 'utf8' });
500
+ await fsPromises.writeFile(`${this.databasePath}/init.sql`, initText, { encoding: 'utf8' });
481
501
  this.logger.log('Finished writing init file');
482
502
  };
483
503
  exports.default = Executor;
@@ -6,8 +6,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.waitForLock = waitForLock;
7
7
  exports.lockFile = lockFile;
8
8
  const promises_1 = __importDefault(require("fs/promises"));
9
- const mtimeUpdateIntervalTime = 2000;
10
- const mtimeLimit = 10000;
9
+ const mtimeUpdateIntervalTime = 2_000;
10
+ const mtimeLimit = 10_000;
11
11
  async function waitForLock(path, options) {
12
12
  const lockPath = `${path}.lock`;
13
13
  let retries = 0;
@@ -0,0 +1,3 @@
1
+ import { LinuxEtcOSRelease } from '../../types';
2
+ declare const _default: LinuxEtcOSRelease;
3
+ export default _default;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const releaseDetails = {};
8
+ if (process.platform === 'linux') {
9
+ const file = fs_1.default.readFileSync('/etc/os-release', 'utf8');
10
+ const entries = file.split('\n');
11
+ for (const entry of entries) {
12
+ const [key, value] = entry.split('=');
13
+ if (typeof key === 'string' && typeof value === 'string') {
14
+ releaseDetails[key] = value.replaceAll('"', '');
15
+ }
16
+ }
17
+ }
18
+ exports.default = releaseDetails;
@@ -1,5 +1,2 @@
1
- import { InternalServerOptions, MySQLVersion } from "../../types";
2
- export default function getBinaryURL(versions: MySQLVersion[], versionToGet: string, options: InternalServerOptions): {
3
- url: string;
4
- version: string;
5
- };
1
+ import { BinaryInfo } from "../../types";
2
+ export default function getBinaryURL(versionToGet: string, currentArch: string): BinaryInfo;
@@ -32,34 +32,99 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
35
38
  Object.defineProperty(exports, "__esModule", { value: true });
36
39
  exports.default = getBinaryURL;
37
40
  const os = __importStar(require("os"));
38
41
  const semver_1 = require("semver");
39
- function getBinaryURL(versions, versionToGet = "x", options) {
40
- let availableVersions = versions;
41
- availableVersions = availableVersions.filter(v => v.arch === options.arch);
42
- if (availableVersions.length === 0)
43
- throw `No MySQL binary could be found for your CPU architecture: ${options.arch}. ${process.platform === 'win32' && process.arch === 'arm64' ? 'This package has detected you are running Windows on ARM. MySQL does not support Windows on ARM. To get this package working, please try setting the "arch" option to "x64".' : ''}`;
44
- availableVersions = availableVersions.filter(v => v.os === process.platform);
45
- if (availableVersions.length === 0)
46
- throw `No MySQL binary could be found for your OS: ${process.platform}`;
47
- availableVersions = availableVersions.filter(v => {
48
- const release = (0, semver_1.coerce)(os.release());
49
- if (!release)
50
- return false;
51
- return (0, semver_1.satisfies)(release.version, v.osKernelVersionsSupported);
42
+ const constants_1 = require("../constants");
43
+ const LinuxOSRelease_1 = __importDefault(require("./LinuxOSRelease"));
44
+ function getBinaryURL(versionToGet = "x", currentArch) {
45
+ let selectedVersions = constants_1.DOWNLOADABLE_MYSQL_VERSIONS.filter(version => (0, semver_1.satisfies)(version, versionToGet));
46
+ if (selectedVersions.length === 0) {
47
+ throw `mysql-memory-server does not support downloading a version of MySQL that fits the following version requirement: ${versionToGet}. This package only supports downloads of MySQL for MySQL >= ${constants_1.DOWNLOADABLE_MYSQL_VERSIONS[0]} <= ${constants_1.DOWNLOADABLE_MYSQL_VERSIONS.at(-1)}. Please check for typos, choose a different version of MySQL to use, or make an issue or pull request on GitHub if you belive this is a bug.`;
48
+ }
49
+ const currentOS = os.platform();
50
+ const OSVersionSupport = constants_1.MYSQL_MIN_OS_SUPPORT[currentOS];
51
+ if (!OSVersionSupport)
52
+ throw `MySQL and/or mysql-memory-server does not support your operating system. Please make sure you are running the latest version of mysql-memory-server or try running on a different operating system or report an issue on GitHub if you believe this is a bug.`;
53
+ const OSSupportVersionRanges = Object.keys(OSVersionSupport);
54
+ selectedVersions = selectedVersions.filter(possibleVersion => {
55
+ const OSKey = OSSupportVersionRanges.find(item => (0, semver_1.satisfies)(possibleVersion, item));
56
+ return !!OSKey;
57
+ });
58
+ if (selectedVersions.length === 0) {
59
+ throw `No version of MySQL could be found that supports your operating system and fits the following version requirement: ${versionToGet}. Please check for typos, choose a different version of MySQL to run, or if you think this is a bug, please report this on GitHub.`;
60
+ }
61
+ const archSupport = constants_1.MYSQL_ARCH_SUPPORT[currentOS][currentArch];
62
+ if (!archSupport) {
63
+ if (currentOS === 'win32' && currentArch === 'arm64')
64
+ throw 'mysql-memory-server has detected you are running Windows on ARM. MySQL does not support Windows on ARM. To get this package working, please try setting the "arch" option to "x64".';
65
+ throw `MySQL and/or mysql-memory-server does not support the CPU architecture you want to use (${currentArch}). Please make sure you are using the latest version of mysql-memory-server or try using a different architecture, or if you believe this is a bug, please report this on GitHub.`;
66
+ }
67
+ selectedVersions = selectedVersions.filter(possibleVersion => (0, semver_1.satisfies)(possibleVersion, archSupport));
68
+ if (selectedVersions.length === 0) {
69
+ throw `No version of MySQL could be found that supports the CPU architecture ${currentArch === os.arch() ? 'for your system' : 'you have chosen'} (${currentArch}). Please try choosing a different version of MySQL, or if you believe this is a bug, please report this on GitHub.`;
70
+ }
71
+ const versionsBeforeOSVersionCheck = selectedVersions.slice();
72
+ const coercedOSRelease = (0, semver_1.coerce)(os.release());
73
+ selectedVersions = selectedVersions.filter(possibleVersion => {
74
+ const OSVersionKey = OSSupportVersionRanges.find(item => (0, semver_1.satisfies)(possibleVersion, item));
75
+ return !(0, semver_1.lt)(coercedOSRelease, OSVersionSupport[OSVersionKey]);
52
76
  });
53
- if (availableVersions.length === 0)
54
- throw `No MySQL binary could be found that supports your OS version: ${os.release()} | ${os.version()}`;
55
- const wantedVersions = availableVersions.filter(v => (0, semver_1.satisfies)(v.version, versionToGet));
56
- if (wantedVersions.length === 0)
57
- throw `No MySQL binary could be found that meets your version requirement: ${versionToGet} for OS ${process.platform} version ${os.release()} on arch ${process.arch}. The available versions for download are: ${availableVersions.map(v => v.version)}`;
77
+ if (selectedVersions.length === 0) {
78
+ const versionKeys = new Set();
79
+ for (const v of versionsBeforeOSVersionCheck) {
80
+ versionKeys.add(OSSupportVersionRanges.find(item => (0, semver_1.satisfies)(v, item)));
81
+ }
82
+ const minVersions = Array.from(versionKeys).map(v => OSVersionSupport[v]);
83
+ //Sorts versions in ascending order
84
+ minVersions.sort((a, b) => a < b ? -1 : 1);
85
+ const minVersion = minVersions[0];
86
+ throw `Your operating system is too out of date to run a version of MySQL that fits the following requirement: ${versionToGet}. The oldest version for your operating system that you would need to get a version that satisfies the version requirement is ${minVersion} but your current operating system is ${coercedOSRelease.version}. Please try changing your MySQL version requirement, updating your OS to a newer version, or if you believe this is a bug, please report this on GitHub.`;
87
+ }
88
+ if (process.platform === 'linux' && LinuxOSRelease_1.default.NAME === 'Ubuntu' && LinuxOSRelease_1.default.VERSION_ID >= '24.04') {
89
+ //Since Ubuntu >= 24.04 uses libaio1t64 instead of libaio, this package has to copy libaio1t64 into a folder that MySQL looks in for dynamically linked libraries with the filename "libaio.so.1".
90
+ //I have not been able to find a suitable folder for libaio1t64 to be copied into for MySQL < 8.0.4, so here we are filtering all versions lower than 8.0.4 since they fail to launch in Ubuntu 24.04.
91
+ //If there is a suitable filepath for libaio1t64 to be copied into for MySQL < 8.0.4 then this check can be removed and these older MySQL versions can run on Ubuntu.
92
+ //Pull requests are welcome for adding >= Ubuntu 24.04 support for MySQL < 8.0.4.
93
+ //A way to get MySQL running on Ubuntu >= 24.04 is to symlink libaio1t64 to the location libaio would be. It is not suitable for this package to be doing that automatically, so instead this package has been copying libaio1t64 into the MySQL binary folder.
94
+ selectedVersions = selectedVersions.filter(v => !(0, semver_1.lt)(v, '8.0.4'));
95
+ }
96
+ if (selectedVersions.length === 0) {
97
+ throw `You are running a version of Ubuntu that is too modern to run any MySQL versions with this package that match the following version requirement: ${versionToGet}. Please choose a newer version of MySQL to use, or if you believe this is a bug please report this on GitHub.`;
98
+ }
58
99
  //Sorts versions in descending order
59
- wantedVersions.sort((a, b) => a.version < b.version ? 1 : a.version === b.version ? 0 : -1);
60
- const v = wantedVersions[0];
100
+ selectedVersions.sort((a, b) => a < b ? 1 : -1);
101
+ const selectedVersion = selectedVersions[0];
102
+ const isRC = (0, semver_1.satisfies)(selectedVersion, constants_1.RC_MYSQL_VERSIONS);
103
+ const isDMR = (0, semver_1.satisfies)(selectedVersion, constants_1.DMR_MYSQL_VERSIONS);
104
+ let fileLocation = '';
105
+ if (currentOS === 'win32') {
106
+ fileLocation = `${(0, semver_1.major)(selectedVersion)}.${(0, semver_1.minor)(selectedVersion)}/mysql-${selectedVersion}${isRC ? '-rc' : isDMR ? '-dmr' : ''}-winx64.zip`;
107
+ }
108
+ else if (currentOS === 'darwin') {
109
+ const MySQLmacOSVersionNameKeys = Object.keys(constants_1.MYSQL_MACOS_VERSIONS_IN_FILENAME);
110
+ const macOSVersionNameKey = MySQLmacOSVersionNameKeys.find(range => (0, semver_1.satisfies)(selectedVersion, range));
111
+ fileLocation = `${(0, semver_1.major)(selectedVersion)}.${(0, semver_1.minor)(selectedVersion)}/mysql-${selectedVersion}${isRC ? '-rc' : isDMR ? '-dmr' : ''}-${constants_1.MYSQL_MACOS_VERSIONS_IN_FILENAME[macOSVersionNameKey]}-${currentArch === 'x64' ? 'x86_64' : 'arm64'}.tar.gz`;
112
+ }
113
+ else if (currentOS === 'linux') {
114
+ const glibcVersionKeys = Object.keys(constants_1.MYSQL_LINUX_GLIBC_VERSIONS);
115
+ const glibcVersionKey = glibcVersionKeys.find(range => (0, semver_1.satisfies)(selectedVersion, range));
116
+ const glibcVersion = constants_1.MYSQL_LINUX_GLIBC_VERSIONS[glibcVersionKey];
117
+ const minimalInstallAvailableKeys = Object.keys(constants_1.MYSQL_LINUX_MINIMAL_INSTALL_AVAILABLE);
118
+ const minimalInstallAvailableKey = minimalInstallAvailableKeys.find(range => (0, semver_1.satisfies)(selectedVersion, range));
119
+ const minimalInstallAvailable = constants_1.MYSQL_LINUX_MINIMAL_INSTALL_AVAILABLE[minimalInstallAvailableKey];
120
+ const fileExtensionObject = constants_1.MYSQL_LINUX_FILE_EXTENSIONS[currentArch];
121
+ const fileExtensionKeys = Object.keys(fileExtensionObject);
122
+ const fileExtensionKey = fileExtensionKeys.find(range => (0, semver_1.satisfies)(selectedVersion, range));
123
+ const fileExtension = constants_1.MYSQL_LINUX_FILE_EXTENSIONS[currentArch][fileExtensionKey];
124
+ fileLocation = `${(0, semver_1.major)(selectedVersion)}.${(0, semver_1.minor)(selectedVersion)}/mysql-${selectedVersion}${isRC ? '-rc' : isDMR ? '-dmr' : ''}-linux-${minimalInstallAvailable !== 'no-glibc-tag' ? `glibc${glibcVersion}-` : ''}${currentArch === 'x64' ? 'x86_64' : 'aarch64'}${minimalInstallAvailable !== 'no' && (process.arch !== 'arm64' ? true : (0, semver_1.satisfies)(selectedVersion, constants_1.MYSQL_LINUX_MINIMAL_INSTALL_AVAILABLE_ARM64)) ? `-minimal${(0, semver_1.satisfies)(selectedVersion, constants_1.MYSQL_LINUX_MINIMAL_REBUILD_VERSIONS) ? '-rebuild' : ''}` : ''}.tar.${fileExtension}`;
125
+ }
61
126
  return {
62
- url: v.url,
63
- version: v.version
127
+ version: selectedVersion,
128
+ url: constants_1.archiveBaseURL + fileLocation
64
129
  };
65
130
  }
@@ -15,10 +15,6 @@ export type ServerOptions = {
15
15
  downloadRetries?: number | undefined;
16
16
  initSQLString?: string | undefined;
17
17
  arch?: "arm64" | "x64" | undefined;
18
- _DO_NOT_USE_deleteDBAfterStopped?: boolean | undefined;
19
- _DO_NOT_USE_dbPath?: string | undefined;
20
- _DO_NOT_USE_binaryDirectoryPath?: string | undefined;
21
- _DO_NOT_USE_cli?: boolean | undefined;
22
18
  };
23
19
  export type InternalServerOptions = {
24
20
  version?: string | undefined;
@@ -35,13 +31,6 @@ export type InternalServerOptions = {
35
31
  downloadRetries: number;
36
32
  initSQLString: string;
37
33
  arch: string;
38
- _DO_NOT_USE_deleteDBAfterStopped: boolean;
39
- _DO_NOT_USE_dbPath: string;
40
- _DO_NOT_USE_binaryDirectoryPath: string;
41
- _DO_NOT_USE_cli: boolean;
42
- };
43
- export type ExecutorOptions = {
44
- logLevel: LOG_LEVEL;
45
34
  };
46
35
  export type ExecuteFileReturn = {
47
36
  error: ExecFileException | null;
@@ -61,13 +50,6 @@ export type MySQLDB = {
61
50
  };
62
51
  stop: () => Promise<void>;
63
52
  };
64
- export type MySQLVersion = {
65
- version: string;
66
- arch: string;
67
- os: string;
68
- osKernelVersionsSupported: string;
69
- url: string;
70
- };
71
53
  export type DownloadedMySQLVersion = {
72
54
  version: string;
73
55
  path: string;
@@ -84,3 +66,17 @@ export type OptionTypeChecks = {
84
66
  definedType: "string" | "boolean" | "number";
85
67
  };
86
68
  };
69
+ export type LinuxEtcOSRelease = {
70
+ PRETTY_NAME?: string;
71
+ NAME?: string;
72
+ VERSION_ID?: string;
73
+ VERSION?: string;
74
+ VERSION_CODENAME?: string;
75
+ ID?: string;
76
+ ID_LIKE?: string;
77
+ UBUNTU_CODENAME?: string;
78
+ };
79
+ export type JSRuntimeVersion = {
80
+ runtimeName: string;
81
+ runtimeVersion: string;
82
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mysql-memory-server",
3
- "version": "1.8.1",
3
+ "version": "1.9.0",
4
4
  "description": "Spin up an ephemeral MySQL database from your JavaScript code",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -16,9 +16,9 @@
16
16
  "memory database"
17
17
  ],
18
18
  "scripts": {
19
- "test": "jest --testPathIgnorePatterns=/stress-tests/ --verbose --colors",
20
- "test:ci": "jest --testPathIgnorePatterns=/stress-tests/ --setupFilesAfterEnv ./ciSetup.js --verbose --colors",
21
- "stress": "jest --runTestsByPath stress-tests/stress.test.ts --setupFilesAfterEnv ./ciSetup.js --verbose --colors"
19
+ "test": "jest --verbose --colors",
20
+ "test:ci": "jest --setupFilesAfterEnv ./ciSetup.js --verbose --colors --runTestsByPath tests/concurrency.test.ts",
21
+ "os-compat:ci": "jest --setupFilesAfterEnv ./ciSetup.js --verbose --colors --runTestsByPath tests/versions.test.ts"
22
22
  },
23
23
  "engines": {
24
24
  "node": ">=16.6.0",
@@ -31,16 +31,16 @@
31
31
  "@babel/preset-env": "^7.25.9",
32
32
  "@babel/preset-typescript": "^7.25.9",
33
33
  "@types/adm-zip": "^0.5.5",
34
- "@types/node": "^22.9.3",
34
+ "@types/node": "^22.10.6",
35
35
  "@types/semver": "^7.5.8",
36
36
  "babel-jest": "^29.7.0",
37
37
  "jest": "^29.7.0",
38
- "mysql2": "^3.11.3",
39
- "typescript": "^5.7.2"
38
+ "mysql2": "^3.12.0",
39
+ "typescript": "^5.7.3"
40
40
  },
41
41
  "dependencies": {
42
42
  "adm-zip": "^0.5.16",
43
- "semver": "^7.6.3",
43
+ "semver": "^7.7.1",
44
44
  "signal-exit": "^4.1.0"
45
45
  },
46
46
  "repository": {