Skip to content

Commit 394aac5

Browse files
build - 1.3.0
1 parent f5454e6 commit 394aac5

File tree

10 files changed

+168
-37
lines changed

10 files changed

+168
-37
lines changed

dist/src/constants.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
declare const CONSTANTS: {
2+
MIN_SUPPORTED_MYSQL: string;
3+
};
4+
export default CONSTANTS;

dist/src/constants.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"use strict";
2+
Object.defineProperty(exports, "__esModule", { value: true });
3+
const CONSTANTS = {
4+
MIN_SUPPORTED_MYSQL: '8.0.20'
5+
};
6+
exports.default = CONSTANTS;

dist/src/index.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const versions_json_1 = __importDefault(require("./versions.json"));
3737
const Downloader_1 = require("./libraries/Downloader");
3838
const crypto_1 = require("crypto");
3939
const path_1 = require("path");
40+
const constants_1 = __importDefault(require("./constants"));
4041
process.on('exit', () => {
4142
AbortSignal_1.default.abort('Process is exiting');
4243
});
@@ -51,14 +52,25 @@ async function createDB(opts) {
5152
username: 'root',
5253
deleteDBAfterStopped: true,
5354
//mysqlmsn = MySQL Memory Server Node.js
54-
dbPath: (0, path_1.normalize)(`${os.tmpdir()}/mysqlmsn/dbs/${(0, crypto_1.randomUUID)().replace(/-/g, '')}`)
55+
dbPath: (0, path_1.normalize)(`${os.tmpdir()}/mysqlmsn/dbs/${(0, crypto_1.randomUUID)().replace(/-/g, '')}`),
56+
ignoreUnsupportedSystemVersion: false
5557
};
5658
const options = { ...defaultOptions, ...opts };
5759
const logger = new Logger_1.default(options.logLevel);
5860
const executor = new Executor_1.default(logger);
5961
const version = await executor.getMySQLVersion(options.version);
62+
const unsupportedMySQLIsInstalled = version && (0, semver_1.lt)(version.version, constants_1.default.MIN_SUPPORTED_MYSQL);
63+
const throwUnsupportedError = unsupportedMySQLIsInstalled && !options.ignoreUnsupportedSystemVersion && !options.version;
64+
if (throwUnsupportedError) {
65+
throw `A version of MySQL is installed on your system that is not supported by this package. If you want to download a MySQL binary instead of getting this error, please set the option "ignoreUnsupportedSystemVersion" to true.`;
66+
}
67+
if (options.version && (0, semver_1.lt)(options.version, constants_1.default.MIN_SUPPORTED_MYSQL)) {
68+
//The difference between the throw here and the throw above is this throw is because the selected "version" is not supported.
69+
//The throw above is because the system-installed MySQL is out of date and "ignoreUnsupportedSystemVersion" is not set to true.
70+
throw `The selected version of MySQL (${options.version}) is not currently supported by this package. Please choose a different version to use.`;
71+
}
6072
logger.log('Version currently installed:', version);
61-
if (version === null || (options.version && !(0, semver_1.satisfies)(version.version, options.version))) {
73+
if (version === null || (options.version && !(0, semver_1.satisfies)(version.version, options.version)) || unsupportedMySQLIsInstalled) {
6274
let binaryInfo;
6375
let binaryFilepath;
6476
try {

dist/src/libraries/Downloader.js

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const path_1 = require("path");
3737
const crypto_1 = require("crypto");
3838
const child_process_1 = require("child_process");
3939
const proper_lockfile_1 = require("proper-lockfile");
40+
const FileLock_1 = require("./FileLock");
4041
function getZipData(entry) {
4142
return new Promise((resolve, reject) => {
4243
entry.getDataAsync((data, err) => {
@@ -157,27 +158,6 @@ function extractBinary(url, archiveLocation, extractedLocation) {
157158
});
158159
});
159160
}
160-
function waitForLock(path, options) {
161-
return new Promise(async (resolve, reject) => {
162-
let retries = 0;
163-
while (retries <= options.lockRetries) {
164-
retries++;
165-
try {
166-
const locked = (0, proper_lockfile_1.checkSync)(path);
167-
if (!locked) {
168-
return resolve();
169-
}
170-
else {
171-
await new Promise(resolve => setTimeout(resolve, options.lockRetryWait));
172-
}
173-
}
174-
catch (e) {
175-
return reject(e);
176-
}
177-
}
178-
reject(`lockRetries has been exceeded. Lock had not been released after ${options.lockRetryWait} * ${options.lockRetries} milliseconds.`);
179-
});
180-
}
181161
function downloadBinary(binaryInfo, options, logger) {
182162
return new Promise(async (resolve, reject) => {
183163
const { url, version } = binaryInfo;
@@ -210,7 +190,7 @@ function downloadBinary(binaryInfo, options, logger) {
210190
catch (e) {
211191
if (String(e) === 'Error: Lock file is already being held') {
212192
logger.log('Waiting for lock for MySQL version', version);
213-
await waitForLock(extractedPath, options);
193+
await (0, FileLock_1.waitForLock)(extractedPath, options);
214194
logger.log('Lock is gone for version', version);
215195
return resolve(binaryPath);
216196
}

dist/src/libraries/Executor.js

Lines changed: 111 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
3030
var __importDefault = (this && this.__importDefault) || function (mod) {
3131
return (mod && mod.__esModule) ? mod : { "default": mod };
3232
};
33-
var _Executor_instances, _Executor_execute, _Executor_killProcess, _Executor_startMySQLProcess, _Executor_setupDataDirectories;
33+
var _Executor_instances, _Executor_execute, _Executor_executeFile, _Executor_killProcess, _Executor_startMySQLProcess, _Executor_setupDataDirectories;
3434
Object.defineProperty(exports, "__esModule", { value: true });
3535
const child_process_1 = require("child_process");
3636
const semver_1 = require("semver");
@@ -40,6 +40,8 @@ const fs = __importStar(require("fs"));
4040
const Port_1 = require("./Port");
4141
const AbortSignal_1 = __importDefault(require("./AbortSignal"));
4242
const path_1 = require("path");
43+
const proper_lockfile_1 = require("proper-lockfile");
44+
const FileLock_1 = require("./FileLock");
4345
class Executor {
4446
constructor(logger) {
4547
_Executor_instances.add(this);
@@ -131,7 +133,8 @@ class Executor {
131133
let retries = 0;
132134
const datadir = (0, path_1.normalize)(`${options.dbPath}/data`);
133135
do {
134-
await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_setupDataDirectories).call(this, options, binaryFilepath, datadir);
136+
await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_setupDataDirectories).call(this, options, binaryFilepath, datadir, true);
137+
this.logger.log('Setting up directories was successful');
135138
const port = (0, Port_1.GenerateRandomPort)();
136139
const mySQLXPort = (0, Port_1.GenerateRandomPort)();
137140
this.logger.log('Using port:', port, 'and MySQLX port:', mySQLXPort, 'on retry:', retries);
@@ -164,6 +167,12 @@ _Executor_instances = new WeakSet(), _Executor_execute = function _Executor_exec
164167
resolve({ error, stdout, stderr });
165168
});
166169
});
170+
}, _Executor_executeFile = function _Executor_executeFile(command, args, cwd) {
171+
return new Promise(resolve => {
172+
(0, child_process_1.execFile)(command, args, { signal: AbortSignal_1.default.signal, cwd }, (error, stdout, stderr) => {
173+
resolve({ stdout, stderr: (error === null || error === void 0 ? void 0 : error.message) || stderr });
174+
});
175+
});
167176
}, _Executor_killProcess = async function _Executor_killProcess(process) {
168177
let killed = false;
169178
if (os.platform() === 'win32') {
@@ -290,25 +299,116 @@ _Executor_instances = new WeakSet(), _Executor_execute = function _Executor_exec
290299
}
291300
});
292301
});
293-
}, _Executor_setupDataDirectories = async function _Executor_setupDataDirectories(options, binaryFilepath, datadir) {
302+
}, _Executor_setupDataDirectories = async function _Executor_setupDataDirectories(options, binaryFilepath, datadir, retry) {
294303
this.logger.log('Created data directory for database at:', datadir);
295304
await fsPromises.mkdir(datadir, { recursive: true });
296-
const { error: err, stderr } = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_execute).call(this, `"${binaryFilepath}" --no-defaults --datadir=${datadir} --initialize-insecure`);
297-
if (err || (stderr && !stderr.includes('InnoDB initialization has ended'))) {
298-
if (process.platform === 'win32' && ((err === null || err === void 0 ? void 0 : err.message.includes('Command failed')) || stderr.includes('Command failed'))) {
299-
this.logger.error(err || stderr);
305+
let stderr;
306+
if (binaryFilepath === 'mysqld') {
307+
const { error, stderr: output } = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_execute).call(this, `mysqld --no-defaults --datadir=${datadir} --initialize-insecure`);
308+
stderr = output;
309+
if (error) {
310+
this.logger.error('An error occurred while initializing database with system-installed MySQL:', error);
311+
throw 'An error occurred while initializing database with system-installed MySQL. Please check the console for more information.';
312+
}
313+
}
314+
else {
315+
let result;
316+
try {
317+
result = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_executeFile).call(this, `${binaryFilepath}`, [`--no-defaults`, `--datadir=${datadir}`, `--initialize-insecure`], (0, path_1.resolve)(`${binaryFilepath}/..`));
318+
}
319+
catch (e) {
320+
this.logger.error('Error occurred from executeFile:', e);
321+
throw e;
322+
}
323+
stderr = result === null || result === void 0 ? void 0 : result.stderr;
324+
}
325+
if (stderr && !stderr.includes('InnoDB initialization has ended')) {
326+
if (process.platform === 'win32' && stderr.includes('Command failed')) {
327+
this.logger.error(stderr);
300328
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.';
301329
}
302-
if (process.platform === 'linux' && ((err === null || err === void 0 ? void 0 : err.message.includes('libaio.so')) || stderr.includes('libaio.so'))) {
303-
this.logger.error(err || stderr);
304-
throw 'The mysqld command failed to run. MySQL needs the libaio package installed on Linux systems to run. Do you have this installed? Learn more at https://dev.mysql.com/doc/refman/en/binary-installation.html';
330+
if (process.platform === 'linux' && stderr.includes('libaio.so')) {
331+
if (binaryFilepath === 'mysqld') {
332+
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';
333+
}
334+
if (retry === false) {
335+
this.logger.error('An error occurred while initializing database:', stderr);
336+
throw 'Tried to copy libaio into lib folder and MySQL is still failing to initialize. Please check the console for more information.';
337+
}
338+
if (binaryFilepath.slice(-16) === 'mysql/bin/mysqld') {
339+
const { error: lderror, stdout, stderr: ldstderr } = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_execute).call(this, 'ldconfig -p');
340+
if (lderror || ldstderr) {
341+
this.logger.error('The following libaio error occurred:', stderr);
342+
this.logger.error('After the libaio error, an ldconfig error occurred:', lderror || ldstderr);
343+
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';
344+
}
345+
const libaioFound = stdout.split('\n').filter(lib => lib.includes('libaio.so.1t64'));
346+
if (!libaioFound.length) {
347+
this.logger.error('Error from launching MySQL:', stderr);
348+
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.';
349+
}
350+
const libaioEntry = libaioFound[0];
351+
const libaioPathIndex = libaioEntry.indexOf('=>');
352+
const libaioSymlinkPath = libaioEntry.slice(libaioPathIndex + 3);
353+
const libaioPath = await fsPromises.realpath(libaioSymlinkPath);
354+
const copyPath = (0, path_1.resolve)(`${binaryFilepath}/../../lib/private/libaio.so.1`);
355+
try {
356+
(0, proper_lockfile_1.lockSync)(copyPath, { realpath: false });
357+
this.logger.log('libaio copy path:', copyPath, '| libaio symlink path:', libaioSymlinkPath, '| libaio actual path:', libaioPath);
358+
let copyError;
359+
try {
360+
await fsPromises.copyFile(libaioPath, copyPath);
361+
}
362+
catch (e) {
363+
copyError = e;
364+
this.logger.error('An error occurred while copying libaio1t64 to lib folder:', e);
365+
try {
366+
await fsPromises.rm(copyPath, { force: true });
367+
}
368+
catch (e) {
369+
this.logger.error('An error occurred while deleting libaio file:', e);
370+
}
371+
}
372+
finally {
373+
try {
374+
(0, proper_lockfile_1.unlockSync)(copyPath, { realpath: false });
375+
}
376+
catch (e) {
377+
this.logger.error('Error unlocking libaio file:', e);
378+
}
379+
if (copyError) {
380+
throw 'An error occurred while copying libaio1t64 to the MySQL lib folder. Please check the console for more details.';
381+
}
382+
//Retry setting up directory now that libaio has been copied
383+
this.logger.log('Retrying directory setup');
384+
await this.deleteDatabaseDirectory(datadir);
385+
await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_setupDataDirectories).call(this, options, binaryFilepath, datadir, false);
386+
return;
387+
}
388+
}
389+
catch (error) {
390+
if (String(error) === 'Error: Lock file is already being held') {
391+
this.logger.log('Waiting for lock for libaio copy');
392+
await (0, FileLock_1.waitForLock)(copyPath, options);
393+
this.logger.log('Lock is gone for libaio copy');
394+
}
395+
this.logger.error('An error occurred from locking libaio section:', error);
396+
throw error;
397+
}
398+
}
399+
else {
400+
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';
401+
}
305402
}
306-
throw err || stderr;
403+
throw stderr;
307404
}
405+
this.logger.log('Creating init text');
308406
let initText = `CREATE DATABASE ${options.dbName};`;
309407
if (options.username !== 'root') {
310408
initText += `\nRENAME USER 'root'@'localhost' TO '${options.username}'@'localhost';`;
311409
}
410+
this.logger.log('Writing init file');
312411
await fsPromises.writeFile(`${options.dbPath}/init.sql`, initText, { encoding: 'utf8' });
412+
this.logger.log('Finished writing init file');
313413
};
314414
exports.default = Executor;

dist/src/libraries/FileLock.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import { InternalServerOptions } from "../../types";
2+
export declare function waitForLock(path: string, options: InternalServerOptions): Promise<void>;

dist/src/libraries/FileLock.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"use strict";
2+
Object.defineProperty(exports, "__esModule", { value: true });
3+
exports.waitForLock = waitForLock;
4+
const proper_lockfile_1 = require("proper-lockfile");
5+
function waitForLock(path, options) {
6+
return new Promise(async (resolve, reject) => {
7+
let retries = 0;
8+
while (retries <= options.lockRetries) {
9+
retries++;
10+
try {
11+
const locked = (0, proper_lockfile_1.checkSync)(path);
12+
if (!locked) {
13+
return resolve();
14+
}
15+
else {
16+
await new Promise(resolve => setTimeout(resolve, options.lockRetryWait));
17+
}
18+
}
19+
catch (e) {
20+
return reject(e);
21+
}
22+
}
23+
reject(`lockRetries has been exceeded. Lock had not been released after ${options.lockRetryWait} * ${options.lockRetries} milliseconds.`);
24+
});
25+
}

dist/types/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export type ServerOptions = {
1111
username?: string;
1212
deleteDBAfterStopped?: boolean;
1313
dbPath?: string;
14+
ignoreUnsupportedSystemVersion?: boolean;
1415
};
1516
export type InternalServerOptions = {
1617
version?: string;
@@ -23,6 +24,7 @@ export type InternalServerOptions = {
2324
username: string;
2425
deleteDBAfterStopped: boolean;
2526
dbPath: string;
27+
ignoreUnsupportedSystemVersion: boolean;
2628
};
2729
export type ExecutorOptions = {
2830
logLevel: LOG_LEVEL;

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mysql-memory-server",
3-
"version": "1.2.0",
3+
"version": "1.3.0",
44
"description": "Spin up an ephemeral MySQL database from your JavaScript code",
55
"main": "dist/src/index.js",
66
"types": "dist/src/index.d.ts",

0 commit comments

Comments
 (0)