From 914823857c7e943699388cfa9eac10d22afad787 Mon Sep 17 00:00:00 2001 From: Sebastian-Webster <84299475+Sebastian-Webster@users.noreply.github.com> Date: Tue, 6 Aug 2024 05:19:11 +1200 Subject: [PATCH] build - 1.0.0 --- dist/src/index.d.ts | 2 + dist/src/index.js | 83 ++++++++++ dist/src/libraries/AbortSignal.d.ts | 2 + dist/src/libraries/AbortSignal.js | 4 + dist/src/libraries/Downloader.d.ts | 4 + dist/src/libraries/Downloader.js | 227 ++++++++++++++++++++++++++ dist/src/libraries/Executor.d.ts | 10 ++ dist/src/libraries/Executor.js | 243 ++++++++++++++++++++++++++++ dist/src/libraries/Logger.d.ts | 9 ++ dist/src/libraries/Logger.js | 28 ++++ dist/src/libraries/Port.d.ts | 1 + dist/src/libraries/Port.js | 6 + dist/src/libraries/Version.d.ts | 5 + dist/src/libraries/Version.js | 50 ++++++ dist/src/versions.json | 212 ++++++++++++++++++++++++ dist/types/index.d.ts | 49 ++++++ dist/types/index.js | 2 + 17 files changed, 937 insertions(+) create mode 100644 dist/src/index.d.ts create mode 100644 dist/src/index.js create mode 100644 dist/src/libraries/AbortSignal.d.ts create mode 100644 dist/src/libraries/AbortSignal.js create mode 100644 dist/src/libraries/Downloader.d.ts create mode 100644 dist/src/libraries/Downloader.js create mode 100644 dist/src/libraries/Executor.d.ts create mode 100644 dist/src/libraries/Executor.js create mode 100644 dist/src/libraries/Logger.d.ts create mode 100644 dist/src/libraries/Logger.js create mode 100644 dist/src/libraries/Port.d.ts create mode 100644 dist/src/libraries/Port.js create mode 100644 dist/src/libraries/Version.d.ts create mode 100644 dist/src/libraries/Version.js create mode 100644 dist/src/versions.json create mode 100644 dist/types/index.d.ts create mode 100644 dist/types/index.js diff --git a/dist/src/index.d.ts b/dist/src/index.d.ts new file mode 100644 index 0000000..40f39a1 --- /dev/null +++ b/dist/src/index.d.ts @@ -0,0 +1,2 @@ +import { ServerOptions } from '../types'; +export declare function createDB(opts?: ServerOptions): Promise; diff --git a/dist/src/index.js b/dist/src/index.js new file mode 100644 index 0000000..9aedced --- /dev/null +++ b/dist/src/index.js @@ -0,0 +1,83 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.createDB = createDB; +const Logger_1 = __importDefault(require("./libraries/Logger")); +const os = __importStar(require("node:os")); +const Executor_1 = __importDefault(require("./libraries/Executor")); +const semver_1 = require("semver"); +const AbortSignal_1 = __importDefault(require("./libraries/AbortSignal")); +const Version_1 = __importDefault(require("./libraries/Version")); +const versions_json_1 = __importDefault(require("./versions.json")); +const Downloader_1 = require("./libraries/Downloader"); +const defaultOptions = { + dbName: 'dbdata', + logLevel: 'ERROR', + portRetries: 10, + downloadBinaryOnce: true, + lockRetries: 1000, + lockRetryWait: 1000 +}; +process.on('exit', () => { + AbortSignal_1.default.abort('Process is exiting'); +}); +async function createDB(opts = defaultOptions) { + const options = { ...defaultOptions, ...opts }; + const logger = new Logger_1.default(options.logLevel); + const executor = new Executor_1.default(logger); + const version = await executor.getMySQLVersion(options.version); + logger.log('Version currently installed:', version); + if (version === null || (options.version && !(0, semver_1.satisfies)(version.version, options.version))) { + let binaryInfo; + let binaryFilepath; + try { + binaryInfo = (0, Version_1.default)(versions_json_1.default, options.version); + logger.log('Downloading binary:', binaryInfo.version, 'from URL:', binaryInfo.url); + } + catch (e) { + logger.error(e); + if (options.version) { + 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!`; + } + 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!`; + } + try { + binaryFilepath = await (0, Downloader_1.downloadBinary)(binaryInfo, options, logger); + } + catch (error) { + logger.error('Failed to download binary'); + throw error; + } + logger.log('Running downloaded binary'); + return await executor.startMySQL(options, binaryFilepath); + } + else { + logger.log(version); + return await executor.startMySQL(options, version.path); + } +} diff --git a/dist/src/libraries/AbortSignal.d.ts b/dist/src/libraries/AbortSignal.d.ts new file mode 100644 index 0000000..7738565 --- /dev/null +++ b/dist/src/libraries/AbortSignal.d.ts @@ -0,0 +1,2 @@ +declare const DBDestroySignal: AbortController; +export default DBDestroySignal; diff --git a/dist/src/libraries/AbortSignal.js b/dist/src/libraries/AbortSignal.js new file mode 100644 index 0000000..36c7599 --- /dev/null +++ b/dist/src/libraries/AbortSignal.js @@ -0,0 +1,4 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const DBDestroySignal = new AbortController(); +exports.default = DBDestroySignal; diff --git a/dist/src/libraries/Downloader.d.ts b/dist/src/libraries/Downloader.d.ts new file mode 100644 index 0000000..7883c17 --- /dev/null +++ b/dist/src/libraries/Downloader.d.ts @@ -0,0 +1,4 @@ +import Logger from './Logger'; +import { BinaryInfo, ServerOptions } from '../../types'; +export declare function downloadVersions(): Promise; +export declare function downloadBinary(binaryInfo: BinaryInfo, options: ServerOptions, logger: Logger): Promise; diff --git a/dist/src/libraries/Downloader.js b/dist/src/libraries/Downloader.js new file mode 100644 index 0000000..377722a --- /dev/null +++ b/dist/src/libraries/Downloader.js @@ -0,0 +1,227 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.downloadVersions = downloadVersions; +exports.downloadBinary = downloadBinary; +const https = __importStar(require("https")); +const fs = __importStar(require("fs")); +const fsPromises = __importStar(require("fs/promises")); +const os = __importStar(require("os")); +const adm_zip_1 = __importDefault(require("adm-zip")); +const path_1 = require("path"); +const crypto_1 = require("crypto"); +const child_process_1 = require("child_process"); +const proper_lockfile_1 = require("proper-lockfile"); +function getZipData(entry) { + return new Promise((resolve, reject) => { + entry.getDataAsync((data, err) => { + if (err) { + reject(err); + } + else { + resolve(data); + } + }); + }); +} +function handleTarExtraction(filepath, extractedPath) { + return new Promise((resolve, reject) => { + (0, child_process_1.exec)(`tar -xf ${filepath} -C ${extractedPath}`, (error, stdout, stderr) => { + if (error || stderr) { + return reject(error || stderr); + } + resolve(); + }); + }); +} +function downloadVersions() { + return new Promise((resolve, reject) => { + let json = ""; + https.get("https://github.com/Sebastian-Webster/mysql-memory-server-nodejs/raw/main/versions.json", function (response) { + response + .on("data", append => json += append) + .on("error", e => { + reject(e); + }) + .on("end", () => { + resolve(json); + }); + }); + }); +} +function downloadFromCDN(url, downloadLocation, logger) { + return new Promise((resolve, reject) => { + const fileStream = fs.createWriteStream(downloadLocation); + fileStream.on('open', () => { + const request = https.get(url, (response) => { + response.pipe(fileStream); + }); + request.on('error', (err) => { + logger.error(err); + fileStream.close(); + fs.unlink(downloadLocation, (err) => { + reject(err); + }); + }); + }); + fileStream.on('finish', () => { + resolve(); + }); + fileStream.on('error', (err) => { + logger.error(err); + fileStream.end(); + fs.unlink(downloadLocation, () => { + reject(err); + }); + }); + }); +} +function extractBinary(url, archiveLocation, extractedLocation) { + return new Promise(async (resolve, reject) => { + const lastDashIndex = url.lastIndexOf('-'); + const fileExtension = url.slice(lastDashIndex).split('.').splice(1).join('.'); + await fsPromises.mkdir(extractedLocation, { recursive: true }); + const folderName = url.split('/').at(-1).replace(`.${fileExtension}`, ''); + if (fileExtension === 'zip') { + //Only Windows MySQL files use the .zip extension + const zip = new adm_zip_1.default(archiveLocation); + const entries = zip.getEntries(); + for (const entry of entries) { + if (entry.isDirectory) { + if (entry.name === folderName) { + await fsPromises.mkdir(`${extractedLocation}/mysql`, { recursive: true }); + } + else { + await fsPromises.mkdir(`${extractedLocation}/${entry.entryName}`, { recursive: true }); + } + } + else { + const data = await getZipData(entry); + await fsPromises.writeFile(`${extractedLocation}/${entry.entryName}`, data); + } + } + try { + await fsPromises.rm(archiveLocation); + } + finally { + fsPromises.rename(`${extractedLocation}/${folderName}`, `${extractedLocation}/mysql`); + return resolve((0, path_1.normalize)(`${extractedLocation}/mysql/bin/mysqld.exe`)); + } + } + handleTarExtraction(archiveLocation, extractedLocation).then(async () => { + try { + await fsPromises.rm(archiveLocation); + } + finally { + fsPromises.rename(`${extractedLocation}/${folderName}`, `${extractedLocation}/mysql`); + resolve(`${extractedLocation}/mysql/bin/mysqld`); + } + }).catch(error => { + reject(`An error occurred while extracting the tar file. Please make sure tar is installed and there is enough storage space for the extraction. The error was: ${error}`); + }); + }); +} +function waitForLock(path, options) { + return new Promise(async (resolve, reject) => { + let retries = 0; + while (retries <= options.lockRetries) { + retries++; + try { + const locked = (0, proper_lockfile_1.checkSync)(path); + if (!locked) { + return resolve(); + } + else { + await new Promise(resolve => setTimeout(resolve, options.lockRetryWait)); + } + } + catch (e) { + return reject(e); + } + } + reject(`lockRetries has been exceeded. Lock had not been released after ${options.lockRetryWait} * ${options.lockRetries} milliseconds.`); + }); +} +function downloadBinary(binaryInfo, options, logger) { + return new Promise(async (resolve, reject) => { + const { url, version } = binaryInfo; + const dirpath = `${os.tmpdir()}/mysqlmsn/binaries`; + logger.log('Binary path:', dirpath); + await fsPromises.mkdir(dirpath, { recursive: true }); + const lastDashIndex = url.lastIndexOf('-'); + const fileExtension = url.slice(lastDashIndex).split('.').splice(1).join('.'); + if (options.downloadBinaryOnce) { + const extractedPath = `${dirpath}/${version}`; + await fsPromises.mkdir(extractedPath, { recursive: true }); + const binaryPath = (0, path_1.normalize)(`${extractedPath}/mysql/bin/mysqld${process.platform === 'win32' ? '.exe' : ''}`); + const binaryExists = fs.existsSync(binaryPath); + if (binaryExists) { + return resolve(binaryPath); + } + try { + (0, proper_lockfile_1.lockSync)(extractedPath); + const archivePath = `${dirpath}/${version}.${fileExtension}`; + await downloadFromCDN(url, archivePath, logger); + await extractBinary(url, archivePath, extractedPath); + try { + (0, proper_lockfile_1.unlockSync)(extractedPath); + } + catch (e) { + return reject(e); + } + return resolve(binaryPath); + } + catch (e) { + if (String(e) === 'Error: Lock file is already being held') { + logger.log('Waiting for lock for MySQL version', version); + await waitForLock(extractedPath, options); + logger.log('Lock is gone for version', version); + return resolve(binaryPath); + } + return reject(e); + } + } + const uuid = (0, crypto_1.randomUUID)(); + const zipFilepath = `${dirpath}/${uuid}.${fileExtension}`; + logger.log('Binary filepath:', zipFilepath); + const extractedPath = `${dirpath}/${uuid}`; + try { + await downloadFromCDN(url, zipFilepath, logger); + } + catch (e) { + reject(e); + } + try { + const binaryPath = await extractBinary(url, zipFilepath, extractedPath); + resolve(binaryPath); + } + catch (e) { + reject(e); + } + }); +} diff --git a/dist/src/libraries/Executor.d.ts b/dist/src/libraries/Executor.d.ts new file mode 100644 index 0000000..b766546 --- /dev/null +++ b/dist/src/libraries/Executor.d.ts @@ -0,0 +1,10 @@ +import Logger from "./Logger"; +import { InstalledMySQLVersion, InternalServerOptions, MySQLDB } from "../../types"; +declare class Executor { + #private; + logger: Logger; + constructor(logger: Logger); + getMySQLVersion(preferredVersion?: string): Promise; + startMySQL(options: InternalServerOptions, binaryFilepath: string): Promise; +} +export default Executor; diff --git a/dist/src/libraries/Executor.js b/dist/src/libraries/Executor.js new file mode 100644 index 0000000..e1dd0a9 --- /dev/null +++ b/dist/src/libraries/Executor.js @@ -0,0 +1,243 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { + if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); + 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"); + return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +var _Executor_instances, _Executor_execute, _Executor_startMySQLProcess; +Object.defineProperty(exports, "__esModule", { value: true }); +const child_process_1 = require("child_process"); +const semver_1 = require("semver"); +const os = __importStar(require("os")); +const fsPromises = __importStar(require("fs/promises")); +const fs = __importStar(require("fs")); +const Port_1 = require("./Port"); +const AbortSignal_1 = __importDefault(require("./AbortSignal")); +const path_1 = require("path"); +const crypto_1 = require("crypto"); +class Executor { + constructor(logger) { + _Executor_instances.add(this); + this.logger = logger; + } + getMySQLVersion(preferredVersion) { + return new Promise(async (resolve, reject) => { + if (process.platform === 'win32') { + try { + const dirs = await fsPromises.readdir(`${process.env.PROGRAMFILES}\\MySQL`); + const servers = dirs.filter(dirname => dirname.includes('MySQL Server')); + if (servers.length === 0) { + return resolve(null); + } + const versions = []; + for (const dir of servers) { + const path = `${process.env.PROGRAMFILES}\\MySQL\\${dir}\\bin\\mysqld`; + const { error, stdout, stderr } = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_execute).call(this, `"${path}" --version`); + if (error || stderr) { + return reject(error || stderr); + } + const verIndex = stdout.indexOf('Ver'); + const version = (0, semver_1.coerce)(stdout.slice(verIndex)); + if (version === null) { + return reject('Could not get MySQL version'); + } + else { + versions.push({ version: version.version, path }); + } + } + if (preferredVersion) { + resolve(versions.find(version => (0, semver_1.satisfies)(version.version, preferredVersion)) || null); + } + else { + versions.sort(); + resolve(versions[0]); + } + } + catch (e) { + this.logger.error('Error occurred while getting installed MySQL version:', e); + resolve(null); + } + } + else { + const { error, stdout, stderr } = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_execute).call(this, 'mysqld --version'); + if (stderr && stderr.includes('not found')) { + resolve(null); + } + else if (error || stderr) { + reject(error || stderr); + } + else { + const version = (0, semver_1.coerce)(stdout); + if (version === null) { + reject('Could not get installed MySQL version'); + } + else { + resolve({ version: version.version, path: 'mysqld' }); + } + } + } + }); + } + startMySQL(options, binaryFilepath) { + return new Promise(async (resolve, reject) => { + //mysqlmsn = MySQL Memory Server Node.js + const dbPath = (0, path_1.normalize)(`${os.tmpdir()}/mysqlmsn/dbs/${(0, crypto_1.randomUUID)().replace(/-/g, '')}`); + const datadir = (0, path_1.normalize)(`${dbPath}/data`); + this.logger.log('Created data directory for database at:', datadir); + await fsPromises.mkdir(datadir, { recursive: true }); + const { error: err, stderr } = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_execute).call(this, `"${binaryFilepath}" --no-defaults --datadir=${datadir} --initialize-insecure`); + if (err || (stderr && !stderr.includes('InnoDB initialization has ended'))) { + if (process.platform === 'win32' && err.message.includes('Command failed')) { + this.logger.error(err || stderr); + return reject('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.'); + } + if (process.platform === 'linux' && err.message.includes('libaio.so')) { + this.logger.error(err || stderr); + 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'); + } + return reject(err || stderr); + } + await fsPromises.writeFile(`${dbPath}/init.sql`, `CREATE DATABASE ${options.dbName};`, { encoding: 'utf8' }); + let retries = 0; + do { + const port = (0, Port_1.GenerateRandomPort)(); + const mySQLXPort = (0, Port_1.GenerateRandomPort)(); + this.logger.log('Using port:', port, 'on retry:', retries); + try { + const resolved = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_startMySQLProcess).call(this, options, port, mySQLXPort, datadir, dbPath, binaryFilepath); + return resolve(resolved); + } + catch (e) { + if (e !== 'Port is already in use') { + return reject(e); + } + retries++; + if (retries < options.portRetries) { + this.logger.warn(`One or both of these ports are already in use: ${port} or ${mySQLXPort}. Now retrying... ${retries}/${options.portRetries} possible retries.`); + } + } + } while (retries < options.portRetries); + reject(`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.`); + }); + } +} +_Executor_instances = new WeakSet(), _Executor_execute = function _Executor_execute(command) { + return new Promise(resolve => { + (0, child_process_1.exec)(command, { signal: AbortSignal_1.default.signal }, (error, stdout, stderr) => { + resolve({ error, stdout, stderr }); + }); + }); +}, _Executor_startMySQLProcess = function _Executor_startMySQLProcess(options, port, mySQLXPort, datadir, dbPath, binaryFilepath) { + const errors = []; + const logFile = `${dbPath}/log.log`; + return new Promise(async (resolve, reject) => { + await fsPromises.rm(logFile, { force: true }); + 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' }); + //resolveFunction is the function that will be called to resolve the promise that stops the database. + //If resolveFunction is not undefined, the database has received a kill signal and data cleanup procedures should run. + //Once ran, resolveFunction will be called. + let resolveFunction; + process.on('close', async (code, signal) => { + try { + await fsPromises.rm(dbPath, { recursive: true, force: true }); + if (binaryFilepath.includes(os.tmpdir()) && !options.downloadBinaryOnce) { + const splitPath = binaryFilepath.split(os.platform() === 'win32' ? '\\' : '/'); + const binariesIndex = splitPath.indexOf('binaries'); + //The path will be the directory path for the binary download + splitPath.splice(binariesIndex + 2); + //Delete the binary folder + await fsPromises.rm(splitPath.join('/'), { force: true, recursive: true }); + } + } + finally { + if (resolveFunction) { + resolveFunction(); + return; + } + if (code === 0) { + return reject('Database exited early'); + } + const errorString = errors.join('\n'); + this.logger.error(errorString); + if (errorString.includes('Address already in use')) { + return reject('Port is already in use'); + } + if (code) { + return reject(errorString); + } + } + }); + process.stderr.on('data', (data) => { + if (!resolveFunction) { + if (Buffer.isBuffer(data)) { + errors.push(data.toString()); + } + else { + errors.push(data); + } + } + }); + fs.watchFile(logFile, async (curr) => { + if (curr.dev !== 0) { + //File exists + const file = await fsPromises.readFile(logFile, { encoding: 'utf8' }); + if (file.includes('started with:')) { + fs.unwatchFile(logFile); + resolve({ + port, + xPort: mySQLXPort, + dbName: options.dbName, + stop: () => { + return new Promise(async (resolve, reject) => { + resolveFunction = resolve; + let killed = false; + if (os.platform() === 'win32') { + const { error, stderr } = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_execute).call(this, `taskkill /pid ${process.pid} /t /f`); + if (!error && !stderr) { + killed = true; + } + else { + this.logger.error(error || stderr); + } + } + else { + killed = process.kill(); + } + if (!killed) { + reject(); + } + }); + } + }); + } + } + }); + }); +}; +exports.default = Executor; diff --git a/dist/src/libraries/Logger.d.ts b/dist/src/libraries/Logger.d.ts new file mode 100644 index 0000000..1bbb28f --- /dev/null +++ b/dist/src/libraries/Logger.d.ts @@ -0,0 +1,9 @@ +import { LOG_LEVEL } from "../../types"; +declare class Logger { + LOG_LEVEL: number; + constructor(level: LOG_LEVEL); + log(...args: any): void; + warn(...args: any): void; + error(...args: any): void; +} +export default Logger; diff --git a/dist/src/libraries/Logger.js b/dist/src/libraries/Logger.js new file mode 100644 index 0000000..708bc9f --- /dev/null +++ b/dist/src/libraries/Logger.js @@ -0,0 +1,28 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const LOG_LEVELS = { + 'LOG': 0, + 'WARN': 1, + 'ERROR': 2 +}; +class Logger { + constructor(level) { + this.LOG_LEVEL = LOG_LEVELS[level]; + } + log(...args) { + if (this.LOG_LEVEL === 0) { + console.log.apply(null, args); + } + } + warn(...args) { + if (this.LOG_LEVEL <= 1) { + console.warn.apply(null, args); + } + } + error(...args) { + if (this.LOG_LEVEL <= 2) { + console.error.apply(null, args); + } + } +} +exports.default = Logger; diff --git a/dist/src/libraries/Port.d.ts b/dist/src/libraries/Port.d.ts new file mode 100644 index 0000000..d46b5ce --- /dev/null +++ b/dist/src/libraries/Port.d.ts @@ -0,0 +1 @@ +export declare function GenerateRandomPort(): number; diff --git a/dist/src/libraries/Port.js b/dist/src/libraries/Port.js new file mode 100644 index 0000000..8ac4191 --- /dev/null +++ b/dist/src/libraries/Port.js @@ -0,0 +1,6 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.GenerateRandomPort = GenerateRandomPort; +function GenerateRandomPort() { + return Math.floor(Math.random() * (65535 - 1024) + 1025); +} diff --git a/dist/src/libraries/Version.d.ts b/dist/src/libraries/Version.d.ts new file mode 100644 index 0000000..23eb6f0 --- /dev/null +++ b/dist/src/libraries/Version.d.ts @@ -0,0 +1,5 @@ +import { MySQLVersion } from "../../types"; +export default function getBinaryURL(versions: MySQLVersion[], versionToGet?: string): { + url: string; + version: string; +}; diff --git a/dist/src/libraries/Version.js b/dist/src/libraries/Version.js new file mode 100644 index 0000000..43dbe50 --- /dev/null +++ b/dist/src/libraries/Version.js @@ -0,0 +1,50 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.default = getBinaryURL; +const os = __importStar(require("os")); +const semver_1 = require("semver"); +function getBinaryURL(versions, versionToGet = "9.x") { + let availableVersions = versions; + availableVersions = availableVersions.filter(v => v.arch === process.arch); + if (availableVersions.length === 0) + throw `No MySQL binary could be found for your CPU architecture: ${process.arch}`; + availableVersions = availableVersions.filter(v => v.os === process.platform); + if (availableVersions.length === 0) + throw `No MySQL binary could be found for your OS: ${process.platform}`; + availableVersions = availableVersions.filter(v => (0, semver_1.satisfies)((0, semver_1.coerce)(os.release()).version, v.osKernelVersionsSupported)); + if (availableVersions.length === 0) + throw `No MySQL binary could be found that supports your OS version: ${os.release()} | ${os.version()}`; + const wantedVersions = availableVersions.filter(v => (0, semver_1.satisfies)(v.version, versionToGet)); + if (wantedVersions.length === 0) + 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)}`; + //Sorts versions in descending order + wantedVersions.sort((a, b) => a.version < b.version ? 1 : a.version === b.version ? 0 : -1); + const v = wantedVersions[0]; + return { + url: v.url, + version: v.version + }; +} diff --git a/dist/src/versions.json b/dist/src/versions.json new file mode 100644 index 0000000..5303210 --- /dev/null +++ b/dist/src/versions.json @@ -0,0 +1,212 @@ +[ + { + "version": "9.0.1", + "arch": "arm64", + "os": "darwin", + "osKernelVersionsSupported": ">22", + "url": "https://cdn.mysql.com//Downloads/MySQL-9.0/mysql-9.0.1-macos14-arm64.tar.gz" + }, + { + "version": "9.0.1", + "arch": "x64", + "os": "darwin", + "osKernelVersionsSupported": ">22", + "url": "https://cdn.mysql.com//Downloads/MySQL-9.0/mysql-9.0.1-macos14-x86_64.tar.gz" + }, + { + "version": "9.0.1", + "arch": "x64", + "os": "linux", + "osKernelVersionsSupported": "*", + "url": "https://cdn.mysql.com//Downloads/MySQL-9.0/mysql-9.0.1-linux-glibc2.17-x86_64-minimal.tar.xz" + }, + { + "version": "9.0.1", + "arch": "arm64", + "os": "linux", + "osKernelVersionsSupported": "*", + "url": "https://cdn.mysql.com//Downloads/MySQL-9.0/mysql-9.0.1-linux-glibc2.17-aarch64-minimal.tar.xz" + }, + { + "version": "9.0.1", + "arch": "x64", + "os": "win32", + "osKernelVersionsSupported": "*", + "url": "https://cdn.mysql.com//Downloads/MySQL-9.0/mysql-9.0.1-winx64.zip" + }, + { + "version": "8.4.2", + "arch": "arm64", + "os": "darwin", + "osKernelVersionsSupported": ">22", + "url": "https://cdn.mysql.com//Downloads/MySQL-8.4/mysql-8.4.2-macos14-arm64.tar.gz" + }, + { + "version": "8.4.2", + "arch": "x64", + "os": "darwin", + "osKernelVersionsSupported": ">22", + "url": "https://cdn.mysql.com//Downloads/MySQL-8.4/mysql-8.4.2-macos14-x86_64.tar.gz" + }, + { + "version": "8.4.2", + "arch": "x64", + "os": "linux", + "osKernelVersionsSupported": "*", + "url": "https://cdn.mysql.com//Downloads/MySQL-8.4/mysql-8.4.2-linux-glibc2.17-x86_64-minimal.tar.xz" + }, + { + "version": "8.4.2", + "arch": "arm64", + "os": "linux", + "osKernelVersionsSupported": "*", + "url": "https://cdn.mysql.com//Downloads/MySQL-8.4/mysql-8.4.2-linux-glibc2.17-aarch64-minimal.tar.xz" + }, + { + "version": "8.4.2", + "arch": "x64", + "os": "win32", + "osKernelVersionsSupported": "*", + "url": "https://cdn.mysql.com//Downloads/MySQL-8.4/mysql-8.4.2-winx64.zip" + }, + { + "version": "8.0.39", + "arch": "arm64", + "os": "darwin", + "osKernelVersionsSupported": ">22", + "url": "https://cdn.mysql.com//Downloads/MySQL-8.0/mysql-8.0.39-macos14-arm64.tar.gz" + }, + { + "version": "8.0.39", + "arch": "x64", + "os": "darwin", + "osKernelVersionsSupported": ">22", + "url": "https://cdn.mysql.com//Downloads/MySQL-8.0/mysql-8.0.39-macos14-x86_64.tar.gz" + }, + { + "version": "8.0.39", + "arch": "x64", + "os": "linux", + "osKernelVersionsSupported": "*", + "url": "https://cdn.mysql.com//Downloads/MySQL-8.0/mysql-8.0.39-linux-glibc2.17-x86_64-minimal.tar.xz" + }, + { + "version": "8.0.39", + "arch": "arm64", + "os": "linux", + "osKernelVersionsSupported": "*", + "url": "https://cdn.mysql.com//Downloads/MySQL-8.0/mysql-8.0.39-linux-glibc2.17-aarch64-minimal.tar.xz" + }, + { + "version": "8.0.39", + "arch": "x64", + "os": "win32", + "osKernelVersionsSupported": "*", + "url": "https://cdn.mysql.com//Downloads/MySQL-8.0/mysql-8.0.39-winx64.zip" + }, + { + "version": "8.1.0", + "arch": "arm64", + "os": "darwin", + "osKernelVersionsSupported": ">22", + "url": "https://cdn.mysql.com//Downloads/MySQL-8.1/mysql-8.1.0-macos13-arm64.tar.gz" + }, + { + "version": "8.1.0", + "arch": "x64", + "os": "darwin", + "osKernelVersionsSupported": ">22", + "url": "https://cdn.mysql.com//Downloads/MySQL-8.1/mysql-8.1.0-macos13-x86_64.tar.gz" + }, + { + "version": "8.1.0", + "arch": "x64", + "os": "linux", + "osKernelVersionsSupported": "*", + "url": "https://cdn.mysql.com//Downloads/MySQL-8.1/mysql-8.1.0-linux-glibc2.17-x86_64-minimal.tar.xz" + }, + { + "version": "8.1.0", + "arch": "arm64", + "os": "linux", + "osKernelVersionsSupported": "*", + "url": "https://cdn.mysql.com//Downloads/MySQL-8.1/mysql-8.1.0-linux-glibc2.17-aarch64-minimal.tar.xz" + }, + { + "version": "8.1.0", + "arch": "x64", + "os": "win32", + "osKernelVersionsSupported": "*", + "url": "https://cdn.mysql.com//Downloads/MySQL-8.1/mysql-8.1.0-winx64.zip" + }, + { + "version": "8.2.0", + "arch": "arm64", + "os": "darwin", + "osKernelVersionsSupported": ">22", + "url": "https://cdn.mysql.com//Downloads/MySQL-8.2/mysql-8.2.0-macos13-arm64.tar.gz" + }, + { + "version": "8.2.0", + "arch": "x64", + "os": "darwin", + "osKernelVersionsSupported": ">22", + "url": "https://cdn.mysql.com//Downloads/MySQL-8.2/mysql-8.2.0-macos13-x86_64.tar.gz" + }, + { + "version": "8.2.0", + "arch": "x64", + "os": "linux", + "osKernelVersionsSupported": "*", + "url": "https://cdn.mysql.com//Downloads/MySQL-8.2/mysql-8.2.0-linux-glibc2.17-x86_64-minimal.tar.xz" + }, + { + "version": "8.2.0", + "arch": "arm64", + "os": "linux", + "osKernelVersionsSupported": "*", + "url": "https://cdn.mysql.com//Downloads/MySQL-8.2/mysql-8.2.0-linux-glibc2.17-aarch64-minimal.tar.xz" + }, + { + "version": "8.2.0", + "arch": "x64", + "os": "win32", + "osKernelVersionsSupported": "*", + "url": "https://cdn.mysql.com//Downloads/MySQL-8.2/mysql-8.2.0-winx64.zip" + }, + { + "version": "8.3.0", + "arch": "arm64", + "os": "darwin", + "osKernelVersionsSupported": ">22", + "url": "https://cdn.mysql.com//Downloads/MySQL-8.3/mysql-8.3.0-macos14-arm64.tar.gz" + }, + { + "version": "8.3.0", + "arch": "x64", + "os": "darwin", + "osKernelVersionsSupported": ">22", + "url": "https://cdn.mysql.com//Downloads/MySQL-8.3/mysql-8.3.0-macos14-x86_64.tar.gz" + }, + { + "version": "8.3.0", + "arch": "x64", + "os": "linux", + "osKernelVersionsSupported": "*", + "url": "https://cdn.mysql.com//Downloads/MySQL-8.3/mysql-8.3.0-linux-glibc2.17-x86_64-minimal.tar.xz" + }, + { + "version": "8.3.0", + "arch": "arm64", + "os": "linux", + "osKernelVersionsSupported": "*", + "url": "https://cdn.mysql.com//Downloads/MySQL-8.3/mysql-8.3.0-linux-glibc2.17-aarch64-minimal.tar.xz" + }, + { + "version": "8.3.0", + "arch": "x64", + "os": "win32", + "osKernelVersionsSupported": "*", + "url": "https://cdn.mysql.com//Downloads/MySQL-8.3/mysql-8.3.0-winx64.zip" + } +] diff --git a/dist/types/index.d.ts b/dist/types/index.d.ts new file mode 100644 index 0000000..2e2cdd3 --- /dev/null +++ b/dist/types/index.d.ts @@ -0,0 +1,49 @@ +import { ExecException } from "child_process"; +export type LOG_LEVEL = 'LOG' | 'WARN' | 'ERROR'; +export type ServerOptions = { + version?: string; + dbName?: string; + logLevel?: LOG_LEVEL; + portRetries?: number; + downloadBinaryOnce?: boolean; + lockRetries?: number; + lockRetryWait?: number; +}; +export type InternalServerOptions = { + version?: string; + dbName: string; + logLevel: LOG_LEVEL; + portRetries: number; + downloadBinaryOnce: boolean; + lockRetries: number; + lockRetryWait: number; +}; +export type ExecutorOptions = { + logLevel: LOG_LEVEL; +}; +export type ExecuteReturn = { + error: ExecException | null; + stdout: string; + stderr: string; +}; +export type MySQLDB = { + port: number; + xPort: number; + dbName: string; + stop: () => Promise; +}; +export type MySQLVersion = { + version: string; + arch: string; + os: string; + osKernelVersionsSupported: string; + url: string; +}; +export type InstalledMySQLVersion = { + version: string; + path: string; +}; +export type BinaryInfo = { + url: string; + version: string; +}; diff --git a/dist/types/index.js b/dist/types/index.js new file mode 100644 index 0000000..c8ad2e5 --- /dev/null +++ b/dist/types/index.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true });