mysql-memory-server 1.0.0 → 1.2.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.
- package/README.md +31 -1
- package/dist/src/index.js +18 -12
- package/dist/src/libraries/Downloader.d.ts +2 -2
- package/dist/src/libraries/Downloader.js +24 -13
- package/dist/src/libraries/Executor.d.ts +1 -0
- package/dist/src/libraries/Executor.js +150 -79
- package/dist/src/libraries/Logger.js +3 -3
- package/dist/src/libraries/Version.js +6 -1
- package/dist/src/versions.json +12 -12
- package/dist/types/index.d.ts +7 -0
- package/package.json +22 -5
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ const db = await createDB()
|
|
|
32
32
|
// Connect to the new database with the port provided
|
|
33
33
|
const connection = await sql.createConnection({
|
|
34
34
|
host: '127.0.0.1',
|
|
35
|
-
user:
|
|
35
|
+
user: db.username,
|
|
36
36
|
port: db.port,
|
|
37
37
|
database: db.dbName
|
|
38
38
|
})
|
|
@@ -58,6 +58,8 @@ The port that the MySQL database is listening on
|
|
|
58
58
|
The port that MySQLX is listening on
|
|
59
59
|
- `dbName: string`
|
|
60
60
|
The database that was created on database initialization
|
|
61
|
+
- `username: string`
|
|
62
|
+
The name of the user to use to login to the database
|
|
61
63
|
- `stop: () => Promise<void>`
|
|
62
64
|
The method to stop the database. The returned promise resolves when the database has successfully stopped.
|
|
63
65
|
|
|
@@ -122,6 +124,34 @@ Default: 1,000
|
|
|
122
124
|
|
|
123
125
|
Description: If `downloadBinaryOnce` is set to `true`, `lockRetryWait` is the number of milliseconds to wait before checking if the lock has been released.
|
|
124
126
|
|
|
127
|
+
- `username: string`
|
|
128
|
+
|
|
129
|
+
Required: No
|
|
130
|
+
|
|
131
|
+
Default: root
|
|
132
|
+
|
|
133
|
+
Description: The username of the user that is used to login to the database.
|
|
134
|
+
|
|
135
|
+
- `deleteDBAfterStopped: boolean`
|
|
136
|
+
|
|
137
|
+
Required: No
|
|
138
|
+
|
|
139
|
+
Default: true
|
|
140
|
+
|
|
141
|
+
Gotchas: This option is intended to be for internal debugging purposes only and not meant for people to use. As such, this option will not follow Semantic Versioning.
|
|
142
|
+
|
|
143
|
+
Description: Changes whether or not the database will be deleted after it has been stopped. If set to `true`, the database WILL be deleted after it has been stopped.
|
|
144
|
+
|
|
145
|
+
- `dataPath: string`
|
|
146
|
+
|
|
147
|
+
Required: No
|
|
148
|
+
|
|
149
|
+
Default: `TMPDIR/mysqlmsn/dbs/UUID` (replacing TMPDIR with the OS temp directory and UUID with a UUIDv4 without seperating dashes)
|
|
150
|
+
|
|
151
|
+
Gotchas: This option is intended to be for internal debugging purposes only and not meant for people to use. As such, this option will not follow Semantic Versioning.
|
|
152
|
+
|
|
153
|
+
Description: The folder to store database-related data in
|
|
154
|
+
|
|
125
155
|
## If using Ubuntu 24.04 and newer
|
|
126
156
|
|
|
127
157
|
Selecting what MySQL version to use is not currently supported on Ubuntu 24.04 and newer. To use this package on Ubuntu 24.04 and newer you must have the `mysql-server` package installed on your system and `ServerOptions.version` must either be the version that is installed on the system or undefined.
|
package/dist/src/index.js
CHANGED
|
@@ -35,18 +35,24 @@ const AbortSignal_1 = __importDefault(require("./libraries/AbortSignal"));
|
|
|
35
35
|
const Version_1 = __importDefault(require("./libraries/Version"));
|
|
36
36
|
const versions_json_1 = __importDefault(require("./versions.json"));
|
|
37
37
|
const Downloader_1 = require("./libraries/Downloader");
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
logLevel: 'ERROR',
|
|
41
|
-
portRetries: 10,
|
|
42
|
-
downloadBinaryOnce: true,
|
|
43
|
-
lockRetries: 1000,
|
|
44
|
-
lockRetryWait: 1000
|
|
45
|
-
};
|
|
38
|
+
const crypto_1 = require("crypto");
|
|
39
|
+
const path_1 = require("path");
|
|
46
40
|
process.on('exit', () => {
|
|
47
41
|
AbortSignal_1.default.abort('Process is exiting');
|
|
48
42
|
});
|
|
49
|
-
async function createDB(opts
|
|
43
|
+
async function createDB(opts) {
|
|
44
|
+
const defaultOptions = {
|
|
45
|
+
dbName: 'dbdata',
|
|
46
|
+
logLevel: 'ERROR',
|
|
47
|
+
portRetries: 10,
|
|
48
|
+
downloadBinaryOnce: true,
|
|
49
|
+
lockRetries: 1000,
|
|
50
|
+
lockRetryWait: 1000,
|
|
51
|
+
username: 'root',
|
|
52
|
+
deleteDBAfterStopped: true,
|
|
53
|
+
//mysqlmsn = MySQL Memory Server Node.js
|
|
54
|
+
dbPath: (0, path_1.normalize)(`${os.tmpdir()}/mysqlmsn/dbs/${(0, crypto_1.randomUUID)().replace(/-/g, '')}`)
|
|
55
|
+
};
|
|
50
56
|
const options = { ...defaultOptions, ...opts };
|
|
51
57
|
const logger = new Logger_1.default(options.logLevel);
|
|
52
58
|
const executor = new Executor_1.default(logger);
|
|
@@ -62,16 +68,16 @@ async function createDB(opts = defaultOptions) {
|
|
|
62
68
|
catch (e) {
|
|
63
69
|
logger.error(e);
|
|
64
70
|
if (options.version) {
|
|
65
|
-
throw `A MySQL version ${options.version} binary could not be found that supports your OS (${os.platform()} | ${os.version()}) and CPU architecture (${os.arch()}). Please check you have the latest version of mysql-memory-server. If the latest version still doesn't support the version you want to use, feel free to make a pull request to add support!`;
|
|
71
|
+
throw `A MySQL version ${options.version} binary could not be found that supports your OS (${os.platform()} | ${os.version()} | ${os.release()}) and CPU architecture (${os.arch()}). Please check you have the latest version of mysql-memory-server. If the latest version still doesn't support the version you want to use, feel free to make a pull request to add support!`;
|
|
66
72
|
}
|
|
67
|
-
throw `A MySQL binary could not be found that supports your OS (${os.platform()} | ${os.version()}) and CPU architecture (${os.arch()}). Please check you have the latest version of mysql-memory-server. If the latest version still doesn't support your OS and CPU architecture, feel free to make a pull request to add support!`;
|
|
73
|
+
throw `A MySQL binary could not be found that supports your OS (${os.platform()} | ${os.version()} | ${os.release()}) and CPU architecture (${os.arch()}). Please check you have the latest version of mysql-memory-server. If the latest version still doesn't support your OS and CPU architecture, feel free to make a pull request to add support!`;
|
|
68
74
|
}
|
|
69
75
|
try {
|
|
70
76
|
binaryFilepath = await (0, Downloader_1.downloadBinary)(binaryInfo, options, logger);
|
|
71
77
|
}
|
|
72
78
|
catch (error) {
|
|
73
79
|
logger.error('Failed to download binary');
|
|
74
|
-
throw error
|
|
80
|
+
throw `Failed to download binary. The error was: "${error}"`;
|
|
75
81
|
}
|
|
76
82
|
logger.log('Running downloaded binary');
|
|
77
83
|
return await executor.startMySQL(options, binaryFilepath);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import Logger from './Logger';
|
|
2
|
-
import { BinaryInfo,
|
|
2
|
+
import { BinaryInfo, InternalServerOptions } from '../../types';
|
|
3
3
|
export declare function downloadVersions(): Promise<string>;
|
|
4
|
-
export declare function downloadBinary(binaryInfo: BinaryInfo, options:
|
|
4
|
+
export declare function downloadBinary(binaryInfo: BinaryInfo, options: InternalServerOptions, logger: Logger): Promise<string>;
|
|
@@ -77,26 +77,31 @@ function downloadVersions() {
|
|
|
77
77
|
function downloadFromCDN(url, downloadLocation, logger) {
|
|
78
78
|
return new Promise((resolve, reject) => {
|
|
79
79
|
const fileStream = fs.createWriteStream(downloadLocation);
|
|
80
|
+
let error;
|
|
80
81
|
fileStream.on('open', () => {
|
|
81
82
|
const request = https.get(url, (response) => {
|
|
82
83
|
response.pipe(fileStream);
|
|
83
84
|
});
|
|
84
85
|
request.on('error', (err) => {
|
|
86
|
+
error = err;
|
|
85
87
|
logger.error(err);
|
|
86
88
|
fileStream.close();
|
|
87
|
-
fs.unlink(downloadLocation, (
|
|
88
|
-
reject(err);
|
|
89
|
+
fs.unlink(downloadLocation, () => {
|
|
90
|
+
reject(err.message);
|
|
89
91
|
});
|
|
90
92
|
});
|
|
91
93
|
});
|
|
92
94
|
fileStream.on('finish', () => {
|
|
93
|
-
|
|
95
|
+
if (!error) {
|
|
96
|
+
resolve();
|
|
97
|
+
}
|
|
94
98
|
});
|
|
95
99
|
fileStream.on('error', (err) => {
|
|
100
|
+
error = err;
|
|
96
101
|
logger.error(err);
|
|
97
102
|
fileStream.end();
|
|
98
103
|
fs.unlink(downloadLocation, () => {
|
|
99
|
-
reject(err);
|
|
104
|
+
reject(err.message);
|
|
100
105
|
});
|
|
101
106
|
});
|
|
102
107
|
});
|
|
@@ -106,24 +111,30 @@ function extractBinary(url, archiveLocation, extractedLocation) {
|
|
|
106
111
|
const lastDashIndex = url.lastIndexOf('-');
|
|
107
112
|
const fileExtension = url.slice(lastDashIndex).split('.').splice(1).join('.');
|
|
108
113
|
await fsPromises.mkdir(extractedLocation, { recursive: true });
|
|
109
|
-
const
|
|
114
|
+
const mySQLFolderName = url.split('/').at(-1);
|
|
115
|
+
if (!mySQLFolderName) {
|
|
116
|
+
return reject(`Folder name is undefined for url: ${url}`);
|
|
117
|
+
}
|
|
118
|
+
const folderName = mySQLFolderName.replace(`.${fileExtension}`, '');
|
|
110
119
|
if (fileExtension === 'zip') {
|
|
111
120
|
//Only Windows MySQL files use the .zip extension
|
|
112
121
|
const zip = new adm_zip_1.default(archiveLocation);
|
|
113
122
|
const entries = zip.getEntries();
|
|
114
123
|
for (const entry of entries) {
|
|
115
|
-
if (entry.
|
|
116
|
-
if (entry.
|
|
117
|
-
|
|
124
|
+
if (entry.entryName.indexOf('..') === -1) {
|
|
125
|
+
if (entry.isDirectory) {
|
|
126
|
+
if (entry.name === folderName) {
|
|
127
|
+
await fsPromises.mkdir(`${extractedLocation}/mysql`, { recursive: true });
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
await fsPromises.mkdir(`${extractedLocation}/${entry.entryName}`, { recursive: true });
|
|
131
|
+
}
|
|
118
132
|
}
|
|
119
133
|
else {
|
|
120
|
-
await
|
|
134
|
+
const data = await getZipData(entry);
|
|
135
|
+
await fsPromises.writeFile(`${extractedLocation}/${entry.entryName}`, data);
|
|
121
136
|
}
|
|
122
137
|
}
|
|
123
|
-
else {
|
|
124
|
-
const data = await getZipData(entry);
|
|
125
|
-
await fsPromises.writeFile(`${extractedLocation}/${entry.entryName}`, data);
|
|
126
|
-
}
|
|
127
138
|
}
|
|
128
139
|
try {
|
|
129
140
|
await fsPromises.rm(archiveLocation);
|
|
@@ -4,6 +4,7 @@ declare class Executor {
|
|
|
4
4
|
#private;
|
|
5
5
|
logger: Logger;
|
|
6
6
|
constructor(logger: Logger);
|
|
7
|
+
deleteDatabaseDirectory(path: string): Promise<void>;
|
|
7
8
|
getMySQLVersion(preferredVersion?: string): Promise<InstalledMySQLVersion | null>;
|
|
8
9
|
startMySQL(options: InternalServerOptions, binaryFilepath: string): Promise<MySQLDB>;
|
|
9
10
|
}
|
|
@@ -30,7 +30,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
30
30
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
31
31
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
32
32
|
};
|
|
33
|
-
var _Executor_instances, _Executor_execute, _Executor_startMySQLProcess;
|
|
33
|
+
var _Executor_instances, _Executor_execute, _Executor_killProcess, _Executor_startMySQLProcess, _Executor_setupDataDirectories;
|
|
34
34
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
35
|
const child_process_1 = require("child_process");
|
|
36
36
|
const semver_1 = require("semver");
|
|
@@ -40,12 +40,34 @@ const fs = __importStar(require("fs"));
|
|
|
40
40
|
const Port_1 = require("./Port");
|
|
41
41
|
const AbortSignal_1 = __importDefault(require("./AbortSignal"));
|
|
42
42
|
const path_1 = require("path");
|
|
43
|
-
const crypto_1 = require("crypto");
|
|
44
43
|
class Executor {
|
|
45
44
|
constructor(logger) {
|
|
46
45
|
_Executor_instances.add(this);
|
|
47
46
|
this.logger = logger;
|
|
48
47
|
}
|
|
48
|
+
async deleteDatabaseDirectory(path) {
|
|
49
|
+
let retries = 0;
|
|
50
|
+
//Maximum wait of 10 seconds | 500ms * 20 retries = 10,000ms = 10 seconds
|
|
51
|
+
const waitTime = 500;
|
|
52
|
+
const maxRetries = 20;
|
|
53
|
+
//Since the database processes are killed instantly (SIGKILL) sometimes the database file handles may still be open
|
|
54
|
+
//This would cause an EBUSY error. Retrying the deletions for 10 seconds should give the OS enough time to close
|
|
55
|
+
//the file handles.
|
|
56
|
+
while (retries <= maxRetries) {
|
|
57
|
+
try {
|
|
58
|
+
await fsPromises.rm(path, { recursive: true, force: true });
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
if (retries === maxRetries) {
|
|
63
|
+
throw e;
|
|
64
|
+
}
|
|
65
|
+
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
66
|
+
retries++;
|
|
67
|
+
this.logger.log('DB data directory deletion failed. Now on retry', retries);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
49
71
|
getMySQLVersion(preferredVersion) {
|
|
50
72
|
return new Promise(async (resolve, reject) => {
|
|
51
73
|
if (process.platform === 'win32') {
|
|
@@ -55,6 +77,7 @@ class Executor {
|
|
|
55
77
|
if (servers.length === 0) {
|
|
56
78
|
return resolve(null);
|
|
57
79
|
}
|
|
80
|
+
this.logger.log(servers);
|
|
58
81
|
const versions = [];
|
|
59
82
|
for (const dir of servers) {
|
|
60
83
|
const path = `${process.env.PROGRAMFILES}\\MySQL\\${dir}\\bin\\mysqld`;
|
|
@@ -104,47 +127,35 @@ class Executor {
|
|
|
104
127
|
}
|
|
105
128
|
});
|
|
106
129
|
}
|
|
107
|
-
startMySQL(options, binaryFilepath) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (process.platform === 'linux' && err.message.includes('libaio.so')) {
|
|
121
|
-
this.logger.error(err || stderr);
|
|
122
|
-
return reject('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');
|
|
123
|
-
}
|
|
124
|
-
return reject(err || stderr);
|
|
130
|
+
async startMySQL(options, binaryFilepath) {
|
|
131
|
+
let retries = 0;
|
|
132
|
+
const datadir = (0, path_1.normalize)(`${options.dbPath}/data`);
|
|
133
|
+
do {
|
|
134
|
+
await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_setupDataDirectories).call(this, options, binaryFilepath, datadir);
|
|
135
|
+
const port = (0, Port_1.GenerateRandomPort)();
|
|
136
|
+
const mySQLXPort = (0, Port_1.GenerateRandomPort)();
|
|
137
|
+
this.logger.log('Using port:', port, 'and MySQLX port:', mySQLXPort, 'on retry:', retries);
|
|
138
|
+
try {
|
|
139
|
+
this.logger.log('Starting MySQL process');
|
|
140
|
+
const resolved = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_startMySQLProcess).call(this, options, port, mySQLXPort, datadir, options.dbPath, binaryFilepath);
|
|
141
|
+
this.logger.log('Starting process was successful');
|
|
142
|
+
return resolved;
|
|
125
143
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
this.logger.log('Using port:', port, 'on retry:', retries);
|
|
132
|
-
try {
|
|
133
|
-
const resolved = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_startMySQLProcess).call(this, options, port, mySQLXPort, datadir, dbPath, binaryFilepath);
|
|
134
|
-
return resolve(resolved);
|
|
144
|
+
catch (e) {
|
|
145
|
+
this.logger.warn('Caught error:', e, `\nRetries: ${retries} | options.portRetries: ${options.portRetries}`);
|
|
146
|
+
if (e !== 'Port is already in use') {
|
|
147
|
+
this.logger.error('Error:', e);
|
|
148
|
+
throw e;
|
|
135
149
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
retries++;
|
|
141
|
-
if (retries < options.portRetries) {
|
|
142
|
-
this.logger.warn(`One or both of these ports are already in use: ${port} or ${mySQLXPort}. Now retrying... ${retries}/${options.portRetries} possible retries.`);
|
|
143
|
-
}
|
|
150
|
+
retries++;
|
|
151
|
+
if (retries <= options.portRetries) {
|
|
152
|
+
this.logger.warn(`One or both of these ports are already in use: ${port} or ${mySQLXPort}. Now retrying... ${retries}/${options.portRetries} possible retries.`);
|
|
144
153
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
154
|
+
else {
|
|
155
|
+
throw `The port has been retried ${options.portRetries} times and a free port could not be found.\nEither try again, or if this is a common issue, increase options.portRetries.`;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
} while (retries <= options.portRetries);
|
|
148
159
|
}
|
|
149
160
|
}
|
|
150
161
|
_Executor_instances = new WeakSet(), _Executor_execute = function _Executor_execute(command) {
|
|
@@ -153,43 +164,84 @@ _Executor_instances = new WeakSet(), _Executor_execute = function _Executor_exec
|
|
|
153
164
|
resolve({ error, stdout, stderr });
|
|
154
165
|
});
|
|
155
166
|
});
|
|
167
|
+
}, _Executor_killProcess = async function _Executor_killProcess(process) {
|
|
168
|
+
let killed = false;
|
|
169
|
+
if (os.platform() === 'win32') {
|
|
170
|
+
const { error, stderr } = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_execute).call(this, `taskkill /pid ${process.pid} /t /f`);
|
|
171
|
+
if (!error && !stderr) {
|
|
172
|
+
killed = true;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
this.logger.error(error || stderr);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
killed = process.kill();
|
|
180
|
+
}
|
|
181
|
+
return killed;
|
|
156
182
|
}, _Executor_startMySQLProcess = function _Executor_startMySQLProcess(options, port, mySQLXPort, datadir, dbPath, binaryFilepath) {
|
|
157
183
|
const errors = [];
|
|
158
184
|
const logFile = `${dbPath}/log.log`;
|
|
185
|
+
const errorLogFile = `${datadir}/errorlog.err`;
|
|
159
186
|
return new Promise(async (resolve, reject) => {
|
|
160
187
|
await fsPromises.rm(logFile, { force: true });
|
|
161
|
-
const process = (0, child_process_1.spawn)(binaryFilepath, ['--no-defaults', `--port=${port}`, `--datadir=${datadir}`, `--mysqlx-port=${mySQLXPort}`, `--mysqlx-socket=${dbPath}/x.sock`, `--socket=${dbPath}/m.sock`, `--general-log-file=${logFile}`, '--general-log=1', `--init-file=${dbPath}/init.sql`, '--bind-address=127.0.0.1', '--innodb-doublewrite=OFF'], { signal: AbortSignal_1.default.signal, killSignal: 'SIGKILL' });
|
|
188
|
+
const process = (0, child_process_1.spawn)(binaryFilepath, ['--no-defaults', `--port=${port}`, `--datadir=${datadir}`, `--mysqlx-port=${mySQLXPort}`, `--mysqlx-socket=${dbPath}/x.sock`, `--socket=${dbPath}/m.sock`, `--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}`], { signal: AbortSignal_1.default.signal, killSignal: 'SIGKILL' });
|
|
162
189
|
//resolveFunction is the function that will be called to resolve the promise that stops the database.
|
|
163
190
|
//If resolveFunction is not undefined, the database has received a kill signal and data cleanup procedures should run.
|
|
164
191
|
//Once ran, resolveFunction will be called.
|
|
165
192
|
let resolveFunction;
|
|
166
193
|
process.on('close', async (code, signal) => {
|
|
194
|
+
let errorLog;
|
|
167
195
|
try {
|
|
168
|
-
await fsPromises.
|
|
169
|
-
if (binaryFilepath.includes(os.tmpdir()) && !options.downloadBinaryOnce) {
|
|
170
|
-
const splitPath = binaryFilepath.split(os.platform() === 'win32' ? '\\' : '/');
|
|
171
|
-
const binariesIndex = splitPath.indexOf('binaries');
|
|
172
|
-
//The path will be the directory path for the binary download
|
|
173
|
-
splitPath.splice(binariesIndex + 2);
|
|
174
|
-
//Delete the binary folder
|
|
175
|
-
await fsPromises.rm(splitPath.join('/'), { force: true, recursive: true });
|
|
176
|
-
}
|
|
196
|
+
errorLog = await fsPromises.readFile(errorLogFile, { encoding: 'utf-8' });
|
|
177
197
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
198
|
+
catch (e) {
|
|
199
|
+
errorLog = `ERROR WHILE READING LOG: ${e}`;
|
|
200
|
+
}
|
|
201
|
+
const portIssue = errorLog.includes("Do you already have another mysqld server running");
|
|
202
|
+
const xPortIssue = errorLog.includes("X Plugin can't bind to it");
|
|
203
|
+
this.logger.log('Exiting because of a port issue:', portIssue, '. MySQL X Plugin failed to bind:', xPortIssue);
|
|
204
|
+
if (portIssue || xPortIssue) {
|
|
205
|
+
this.logger.log('Error log when exiting for port in use error:', errorLog);
|
|
206
|
+
try {
|
|
207
|
+
await this.deleteDatabaseDirectory(options.dbPath);
|
|
182
208
|
}
|
|
183
|
-
|
|
184
|
-
|
|
209
|
+
catch (e) {
|
|
210
|
+
this.logger.error(e);
|
|
211
|
+
return reject(`MySQL failed to listen on a certain port. To restart MySQL with a different port, the database directory needed to be deleted. An error occurred while deleting the database directory. Aborting. The error was: ${e}`);
|
|
185
212
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
213
|
+
return reject('Port is already in use');
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
if (options.deleteDBAfterStopped) {
|
|
217
|
+
await this.deleteDatabaseDirectory(dbPath);
|
|
190
218
|
}
|
|
191
|
-
|
|
192
|
-
|
|
219
|
+
}
|
|
220
|
+
finally {
|
|
221
|
+
try {
|
|
222
|
+
if (binaryFilepath.includes(os.tmpdir()) && !options.downloadBinaryOnce) {
|
|
223
|
+
const splitPath = binaryFilepath.split(os.platform() === 'win32' ? '\\' : '/');
|
|
224
|
+
const binariesIndex = splitPath.indexOf('binaries');
|
|
225
|
+
//The path will be the directory path for the binary download
|
|
226
|
+
splitPath.splice(binariesIndex + 2);
|
|
227
|
+
//Delete the binary folder
|
|
228
|
+
await fsPromises.rm(splitPath.join('/'), { force: true, recursive: true });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
finally {
|
|
232
|
+
if (resolveFunction) {
|
|
233
|
+
resolveFunction();
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const errorString = errors.join('\n');
|
|
237
|
+
if (code === 0) {
|
|
238
|
+
return reject(`Database exited early.\nError log: ${errorLog}\nError string: "${errorString}`);
|
|
239
|
+
}
|
|
240
|
+
if (code) {
|
|
241
|
+
const errorMessage = `The database exited early with code ${code}. The error log was:\n${errorLog}\nThe error string was: "${errorString}".`;
|
|
242
|
+
this.logger.error(errorMessage);
|
|
243
|
+
return reject(errorMessage);
|
|
244
|
+
}
|
|
193
245
|
}
|
|
194
246
|
}
|
|
195
247
|
});
|
|
@@ -203,32 +255,31 @@ _Executor_instances = new WeakSet(), _Executor_execute = function _Executor_exec
|
|
|
203
255
|
}
|
|
204
256
|
}
|
|
205
257
|
});
|
|
206
|
-
fs.watchFile(
|
|
258
|
+
fs.watchFile(errorLogFile, async (curr) => {
|
|
207
259
|
if (curr.dev !== 0) {
|
|
208
260
|
//File exists
|
|
209
|
-
const file = await fsPromises.readFile(
|
|
210
|
-
if (file.includes('
|
|
211
|
-
|
|
261
|
+
const file = await fsPromises.readFile(errorLogFile, { encoding: 'utf8' });
|
|
262
|
+
if (file.includes("X Plugin can't bind to it")) {
|
|
263
|
+
//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
|
|
264
|
+
//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.
|
|
265
|
+
//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
|
|
266
|
+
//cannot bind to an address. If there is a way to prevent server startup when binding fails, this workaround can be removed.
|
|
267
|
+
const killed = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_killProcess).call(this, process);
|
|
268
|
+
if (!killed) {
|
|
269
|
+
reject('Failed to kill MySQL process to retry listening on a free port.');
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
else if (file.includes('ready for connections. Version:')) {
|
|
273
|
+
fs.unwatchFile(errorLogFile);
|
|
212
274
|
resolve({
|
|
213
275
|
port,
|
|
214
276
|
xPort: mySQLXPort,
|
|
215
277
|
dbName: options.dbName,
|
|
278
|
+
username: options.username,
|
|
216
279
|
stop: () => {
|
|
217
280
|
return new Promise(async (resolve, reject) => {
|
|
218
281
|
resolveFunction = resolve;
|
|
219
|
-
|
|
220
|
-
if (os.platform() === 'win32') {
|
|
221
|
-
const { error, stderr } = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_execute).call(this, `taskkill /pid ${process.pid} /t /f`);
|
|
222
|
-
if (!error && !stderr) {
|
|
223
|
-
killed = true;
|
|
224
|
-
}
|
|
225
|
-
else {
|
|
226
|
-
this.logger.error(error || stderr);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
else {
|
|
230
|
-
killed = process.kill();
|
|
231
|
-
}
|
|
282
|
+
const killed = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_killProcess).call(this, process);
|
|
232
283
|
if (!killed) {
|
|
233
284
|
reject();
|
|
234
285
|
}
|
|
@@ -239,5 +290,25 @@ _Executor_instances = new WeakSet(), _Executor_execute = function _Executor_exec
|
|
|
239
290
|
}
|
|
240
291
|
});
|
|
241
292
|
});
|
|
293
|
+
}, _Executor_setupDataDirectories = async function _Executor_setupDataDirectories(options, binaryFilepath, datadir) {
|
|
294
|
+
this.logger.log('Created data directory for database at:', datadir);
|
|
295
|
+
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);
|
|
300
|
+
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.';
|
|
301
|
+
}
|
|
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';
|
|
305
|
+
}
|
|
306
|
+
throw err || stderr;
|
|
307
|
+
}
|
|
308
|
+
let initText = `CREATE DATABASE ${options.dbName};`;
|
|
309
|
+
if (options.username !== 'root') {
|
|
310
|
+
initText += `\nRENAME USER 'root'@'localhost' TO '${options.username}'@'localhost';`;
|
|
311
|
+
}
|
|
312
|
+
await fsPromises.writeFile(`${options.dbPath}/init.sql`, initText, { encoding: 'utf8' });
|
|
242
313
|
};
|
|
243
314
|
exports.default = Executor;
|
|
@@ -11,17 +11,17 @@ class Logger {
|
|
|
11
11
|
}
|
|
12
12
|
log(...args) {
|
|
13
13
|
if (this.LOG_LEVEL === 0) {
|
|
14
|
-
console.log
|
|
14
|
+
console.log(...args);
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
warn(...args) {
|
|
18
18
|
if (this.LOG_LEVEL <= 1) {
|
|
19
|
-
console.warn
|
|
19
|
+
console.warn(...args);
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
error(...args) {
|
|
23
23
|
if (this.LOG_LEVEL <= 2) {
|
|
24
|
-
console.error
|
|
24
|
+
console.error(...args);
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -34,7 +34,12 @@ function getBinaryURL(versions, versionToGet = "9.x") {
|
|
|
34
34
|
availableVersions = availableVersions.filter(v => v.os === process.platform);
|
|
35
35
|
if (availableVersions.length === 0)
|
|
36
36
|
throw `No MySQL binary could be found for your OS: ${process.platform}`;
|
|
37
|
-
availableVersions = availableVersions.filter(v =>
|
|
37
|
+
availableVersions = availableVersions.filter(v => {
|
|
38
|
+
const release = (0, semver_1.coerce)(os.release());
|
|
39
|
+
if (!release)
|
|
40
|
+
return false;
|
|
41
|
+
return (0, semver_1.satisfies)(release.version, v.osKernelVersionsSupported);
|
|
42
|
+
});
|
|
38
43
|
if (availableVersions.length === 0)
|
|
39
44
|
throw `No MySQL binary could be found that supports your OS version: ${os.release()} | ${os.version()}`;
|
|
40
45
|
const wantedVersions = availableVersions.filter(v => (0, semver_1.satisfies)(v.version, versionToGet));
|
package/dist/src/versions.json
CHANGED
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
"version": "9.0.1",
|
|
4
4
|
"arch": "arm64",
|
|
5
5
|
"os": "darwin",
|
|
6
|
-
"osKernelVersionsSupported": "
|
|
6
|
+
"osKernelVersionsSupported": ">=22",
|
|
7
7
|
"url": "https://cdn.mysql.com//Downloads/MySQL-9.0/mysql-9.0.1-macos14-arm64.tar.gz"
|
|
8
8
|
},
|
|
9
9
|
{
|
|
10
10
|
"version": "9.0.1",
|
|
11
11
|
"arch": "x64",
|
|
12
12
|
"os": "darwin",
|
|
13
|
-
"osKernelVersionsSupported": "
|
|
13
|
+
"osKernelVersionsSupported": ">=22",
|
|
14
14
|
"url": "https://cdn.mysql.com//Downloads/MySQL-9.0/mysql-9.0.1-macos14-x86_64.tar.gz"
|
|
15
15
|
},
|
|
16
16
|
{
|
|
@@ -38,14 +38,14 @@
|
|
|
38
38
|
"version": "8.4.2",
|
|
39
39
|
"arch": "arm64",
|
|
40
40
|
"os": "darwin",
|
|
41
|
-
"osKernelVersionsSupported": "
|
|
41
|
+
"osKernelVersionsSupported": ">=22",
|
|
42
42
|
"url": "https://cdn.mysql.com//Downloads/MySQL-8.4/mysql-8.4.2-macos14-arm64.tar.gz"
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
45
|
"version": "8.4.2",
|
|
46
46
|
"arch": "x64",
|
|
47
47
|
"os": "darwin",
|
|
48
|
-
"osKernelVersionsSupported": "
|
|
48
|
+
"osKernelVersionsSupported": ">=22",
|
|
49
49
|
"url": "https://cdn.mysql.com//Downloads/MySQL-8.4/mysql-8.4.2-macos14-x86_64.tar.gz"
|
|
50
50
|
},
|
|
51
51
|
{
|
|
@@ -73,14 +73,14 @@
|
|
|
73
73
|
"version": "8.0.39",
|
|
74
74
|
"arch": "arm64",
|
|
75
75
|
"os": "darwin",
|
|
76
|
-
"osKernelVersionsSupported": "
|
|
76
|
+
"osKernelVersionsSupported": ">=22",
|
|
77
77
|
"url": "https://cdn.mysql.com//Downloads/MySQL-8.0/mysql-8.0.39-macos14-arm64.tar.gz"
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
80
|
"version": "8.0.39",
|
|
81
81
|
"arch": "x64",
|
|
82
82
|
"os": "darwin",
|
|
83
|
-
"osKernelVersionsSupported": "
|
|
83
|
+
"osKernelVersionsSupported": ">=22",
|
|
84
84
|
"url": "https://cdn.mysql.com//Downloads/MySQL-8.0/mysql-8.0.39-macos14-x86_64.tar.gz"
|
|
85
85
|
},
|
|
86
86
|
{
|
|
@@ -108,14 +108,14 @@
|
|
|
108
108
|
"version": "8.1.0",
|
|
109
109
|
"arch": "arm64",
|
|
110
110
|
"os": "darwin",
|
|
111
|
-
"osKernelVersionsSupported": "
|
|
111
|
+
"osKernelVersionsSupported": ">=22",
|
|
112
112
|
"url": "https://cdn.mysql.com//Downloads/MySQL-8.1/mysql-8.1.0-macos13-arm64.tar.gz"
|
|
113
113
|
},
|
|
114
114
|
{
|
|
115
115
|
"version": "8.1.0",
|
|
116
116
|
"arch": "x64",
|
|
117
117
|
"os": "darwin",
|
|
118
|
-
"osKernelVersionsSupported": "
|
|
118
|
+
"osKernelVersionsSupported": ">=22",
|
|
119
119
|
"url": "https://cdn.mysql.com//Downloads/MySQL-8.1/mysql-8.1.0-macos13-x86_64.tar.gz"
|
|
120
120
|
},
|
|
121
121
|
{
|
|
@@ -143,14 +143,14 @@
|
|
|
143
143
|
"version": "8.2.0",
|
|
144
144
|
"arch": "arm64",
|
|
145
145
|
"os": "darwin",
|
|
146
|
-
"osKernelVersionsSupported": "
|
|
146
|
+
"osKernelVersionsSupported": ">=22",
|
|
147
147
|
"url": "https://cdn.mysql.com//Downloads/MySQL-8.2/mysql-8.2.0-macos13-arm64.tar.gz"
|
|
148
148
|
},
|
|
149
149
|
{
|
|
150
150
|
"version": "8.2.0",
|
|
151
151
|
"arch": "x64",
|
|
152
152
|
"os": "darwin",
|
|
153
|
-
"osKernelVersionsSupported": "
|
|
153
|
+
"osKernelVersionsSupported": ">=22",
|
|
154
154
|
"url": "https://cdn.mysql.com//Downloads/MySQL-8.2/mysql-8.2.0-macos13-x86_64.tar.gz"
|
|
155
155
|
},
|
|
156
156
|
{
|
|
@@ -178,14 +178,14 @@
|
|
|
178
178
|
"version": "8.3.0",
|
|
179
179
|
"arch": "arm64",
|
|
180
180
|
"os": "darwin",
|
|
181
|
-
"osKernelVersionsSupported": "
|
|
181
|
+
"osKernelVersionsSupported": ">=22",
|
|
182
182
|
"url": "https://cdn.mysql.com//Downloads/MySQL-8.3/mysql-8.3.0-macos14-arm64.tar.gz"
|
|
183
183
|
},
|
|
184
184
|
{
|
|
185
185
|
"version": "8.3.0",
|
|
186
186
|
"arch": "x64",
|
|
187
187
|
"os": "darwin",
|
|
188
|
-
"osKernelVersionsSupported": "
|
|
188
|
+
"osKernelVersionsSupported": ">=22",
|
|
189
189
|
"url": "https://cdn.mysql.com//Downloads/MySQL-8.3/mysql-8.3.0-macos14-x86_64.tar.gz"
|
|
190
190
|
},
|
|
191
191
|
{
|
package/dist/types/index.d.ts
CHANGED
|
@@ -8,6 +8,9 @@ export type ServerOptions = {
|
|
|
8
8
|
downloadBinaryOnce?: boolean;
|
|
9
9
|
lockRetries?: number;
|
|
10
10
|
lockRetryWait?: number;
|
|
11
|
+
username?: string;
|
|
12
|
+
deleteDBAfterStopped?: boolean;
|
|
13
|
+
dbPath?: string;
|
|
11
14
|
};
|
|
12
15
|
export type InternalServerOptions = {
|
|
13
16
|
version?: string;
|
|
@@ -17,6 +20,9 @@ export type InternalServerOptions = {
|
|
|
17
20
|
downloadBinaryOnce: boolean;
|
|
18
21
|
lockRetries: number;
|
|
19
22
|
lockRetryWait: number;
|
|
23
|
+
username: string;
|
|
24
|
+
deleteDBAfterStopped: boolean;
|
|
25
|
+
dbPath: string;
|
|
20
26
|
};
|
|
21
27
|
export type ExecutorOptions = {
|
|
22
28
|
logLevel: LOG_LEVEL;
|
|
@@ -30,6 +36,7 @@ export type MySQLDB = {
|
|
|
30
36
|
port: number;
|
|
31
37
|
xPort: number;
|
|
32
38
|
dbName: string;
|
|
39
|
+
username: string;
|
|
33
40
|
stop: () => Promise<void>;
|
|
34
41
|
};
|
|
35
42
|
export type MySQLVersion = {
|
package/package.json
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mysql-memory-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.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",
|
|
7
7
|
"files": [
|
|
8
8
|
"/dist"
|
|
9
9
|
],
|
|
10
|
-
"keywords": [
|
|
10
|
+
"keywords": [
|
|
11
|
+
"MySQL",
|
|
12
|
+
"ephemeral database",
|
|
13
|
+
"continuous integration",
|
|
14
|
+
"CI",
|
|
15
|
+
"CI/CD",
|
|
16
|
+
"memory database"
|
|
17
|
+
],
|
|
11
18
|
"scripts": {
|
|
12
|
-
"test": "jest"
|
|
19
|
+
"test": "jest --testPathIgnorePatterns=/stress-tests/",
|
|
20
|
+
"test:ci": "jest --testPathIgnorePatterns=/stress-tests/ --setupFilesAfterEnv ./ciSetup.js",
|
|
21
|
+
"stress": "jest --runTestsByPath stress-tests/stress.test.ts --setupFilesAfterEnv ./ciSetup.js"
|
|
13
22
|
},
|
|
14
23
|
"author": "Sebastian-Webster",
|
|
15
24
|
"license": "MIT",
|
|
@@ -27,8 +36,16 @@
|
|
|
27
36
|
"typescript": "^5.5.4"
|
|
28
37
|
},
|
|
29
38
|
"dependencies": {
|
|
30
|
-
"adm-zip": "^0.5.
|
|
39
|
+
"adm-zip": "^0.5.15",
|
|
31
40
|
"proper-lockfile": "^4.1.2",
|
|
32
41
|
"semver": "^7.6.3"
|
|
33
|
-
}
|
|
42
|
+
},
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "https://github.com/Sebastian-Webster/mysql-memory-server-nodejs.git"
|
|
46
|
+
},
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/Sebastian-Webster/mysql-memory-server-nodejs/issues"
|
|
49
|
+
},
|
|
50
|
+
"homepage": "https://github.com/Sebastian-Webster/mysql-memory-server-nodejs"
|
|
34
51
|
}
|