UNPKG

mongodb-memory-server-core

Version:

MongoDB Server for testing (core package, without autodownload). The server will allow you to connect your favourite ODM or client library to the MongoDB Server and run parallel integration tests isolated from each other.

281 lines 14.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DryMongoBinary = void 0; const tslib_1 = require("tslib"); const debug_1 = tslib_1.__importDefault(require("debug")); const resolveConfig_1 = require("./resolveConfig"); const utils_1 = require("./utils"); const path = tslib_1.__importStar(require("path")); const os_1 = require("os"); const find_cache_dir_1 = tslib_1.__importDefault(require("find-cache-dir")); const getos_1 = require("./getos"); const errors_1 = require("./errors"); const MongoBinaryDownloadUrl_1 = require("./MongoBinaryDownloadUrl"); const log = (0, debug_1.default)('MongoMS:DryMongoBinary'); /** * Locate an Binary, without downloading / locking */ class DryMongoBinary { /** * Binaries already found, values are: [Version, Path] */ static binaryCache = new Map(); /** * Try to locate an existing binary * @returns The Path to an Binary Found, or undefined */ static async locateBinary(opts) { log(`locateBinary: Trying to locate Binary for version "${opts.version}"`); const useOpts = await this.generateOptions(opts); if (useOpts.systemBinary) { log(`locateBinary: env "SYSTEM_BINARY" was provided with value: "${useOpts.systemBinary}"`); const systemReturn = await this.getSystemPath(path.resolve(useOpts.systemBinary)); if ((0, utils_1.isNullOrUndefined)(systemReturn)) { throw new errors_1.BinaryNotFoundError(useOpts.systemBinary, ' (systemBinary)'); } return systemReturn; } if (this.binaryCache.has(opts.version)) { const binary = this.binaryCache.get(opts.version); log(`locateBinary: Requested Version found in cache: "[${opts.version}, ${binary}]"`); return binary; } log('locateBinary: running generateDownloadPath'); const returnValue = await this.generateDownloadPath(useOpts); if (!returnValue[0]) { log('locateBinary: could not find a existing binary'); return undefined; } // check for the race-condition of "extraction started, but not finished" // or said differently, the file "exists" but is not fully extracted yet // see https://github.com/typegoose/mongodb-memory-server/issues/872 if (returnValue[0] && (await (0, utils_1.pathExists)((0, utils_1.lockfilePath)(path.dirname(returnValue[1]), useOpts.version)))) { log('locateBinary: binary found, but also a download-lock, trying to resolve lock'); return undefined; } log(`locateBinary: found binary at "${returnValue[1]}"`); this.binaryCache.set(opts.version, returnValue[1]); return returnValue[1]; } /** * Ensure the given options fulfill {@link DryMongoBinaryOptions} by defaulting them * @param opts The options to ensure * @returns The ensured options */ static getEnsuredOptions(opts) { const defaultVersion = (0, resolveConfig_1.resolveConfig)(resolveConfig_1.ResolveConfigVariables.VERSION) ?? resolveConfig_1.DEFAULT_VERSION; return (0, utils_1.isNullOrUndefined)(opts) ? { version: defaultVersion } : { ...opts, version: opts.version || defaultVersion }; } /** * Generate All required options for the binary name / path generation */ static async generateOptions(opts) { log('generateOptions'); const ensuredOpts = DryMongoBinary.getEnsuredOptions(opts); const final = { version: ensuredOpts.version, downloadDir: (0, resolveConfig_1.resolveConfig)(resolveConfig_1.ResolveConfigVariables.DOWNLOAD_DIR) || ensuredOpts.downloadDir || '', os: ensuredOpts.os ?? (await (0, getos_1.getOS)()), platform: ensuredOpts.platform || (0, os_1.platform)(), arch: ensuredOpts.arch || (0, os_1.arch)(), systemBinary: (0, resolveConfig_1.resolveConfig)(resolveConfig_1.ResolveConfigVariables.SYSTEM_BINARY) || ensuredOpts.systemBinary || '', }; final.downloadDir = path.dirname((await this.generateDownloadPath(final))[1]); // if truthy if ((0, resolveConfig_1.resolveConfig)(resolveConfig_1.ResolveConfigVariables.ARCHIVE_NAME) || (0, resolveConfig_1.resolveConfig)(resolveConfig_1.ResolveConfigVariables.DOWNLOAD_URL)) { // "DOWNLOAD_URL" will be used over "ARCHIVE_NAME" // the "as string" cast is there because it is already checked that one of the 2 exists, and "resolveConfig" ensures it only returns strings const input = ((0, resolveConfig_1.resolveConfig)(resolveConfig_1.ResolveConfigVariables.DOWNLOAD_URL) || (0, resolveConfig_1.resolveConfig)(resolveConfig_1.ResolveConfigVariables.ARCHIVE_NAME)); log(`generateOptions: ARCHIVE_NAME or DOWNLOAD_URL defined, generating options based on that (input: "${input}")`); return this.parseArchiveNameRegex(input, final); } return final; } /** * Parse "input" into DryMongoBinaryOptions * @param input The Input to be parsed with the regex * @param opts The Options which will be augmented with "input" * @returns The Augmented options */ static parseArchiveNameRegex(input, opts) { log(`parseArchiveNameRegex (input: "${input}")`); const archiveMatches = /mongodb-(?<platform>linux|win32|osx|macos)(?:-ssl-|-)(?<arch>\w{4,})(?:-(?<dist>\w+)|)(?:-ssl-|-)(?:v|)(?<version>[\d.]+(?:-latest|))\./gim.exec(input); (0, utils_1.assertion)(!(0, utils_1.isNullOrUndefined)(archiveMatches), new errors_1.NoRegexMatchError('input')); // this error is kinda impossible to test, because the regex we use either has matches that are groups or no matches (0, utils_1.assertion)(!(0, utils_1.isNullOrUndefined)(archiveMatches.groups), new errors_1.NoRegexMatchError('input', 'groups')); const groups = archiveMatches.groups; (0, utils_1.assertion)(typeof groups.version === 'string' && groups.version.length > 1, new errors_1.ParseArchiveRegexError('version')); // the following 2 assertions are hard to test, because the regex has restrictions that are more strict than the assertions (0, utils_1.assertion)(typeof groups.platform === 'string' && groups.platform.length > 1, new errors_1.ParseArchiveRegexError('platform')); (0, utils_1.assertion)(typeof groups.arch === 'string' && groups.arch.length >= 4, new errors_1.ParseArchiveRegexError('arch')); opts.version = groups.version; opts.arch = groups.arch; if (groups.platform === 'linux') { const distMatches = groups.dist ? /([a-z]+)(\d*)/gim.exec(groups.dist) : null; opts.os = { os: 'linux', dist: typeof distMatches?.[1] === 'string' ? distMatches[1] : 'unknown', // "release" should be able to be discarded in this case release: '', }; } else { opts.os = { os: groups.platform }; } return opts; } /** * Get the full path with filename * @returns Absoulte Path with FileName */ static async getBinaryName(opts) { log('getBinaryName'); let binaryName; if ((0, resolveConfig_1.envToBool)((0, resolveConfig_1.resolveConfig)(resolveConfig_1.ResolveConfigVariables.USE_ARCHIVE_NAME_FOR_BINARY_NAME))) { const archiveName = await new MongoBinaryDownloadUrl_1.MongoBinaryDownloadUrl(opts).getArchiveName(); binaryName = path.parse(archiveName).name; } else { const addExe = opts.platform === 'win32' ? '.exe' : ''; const dist = (0, getos_1.isLinuxOS)(opts.os) ? opts.os.dist : opts.os.os; binaryName = `mongod-${opts.arch}-${dist}-${opts.version}${addExe}`; } return binaryName; } /** * Combine basePath with binaryName */ static combineBinaryName(basePath, binaryName) { log('combineBinaryName'); return path.resolve(basePath, binaryName); } /** * Probe if the provided "systemBinary" is an existing path * @param systemBinary The Path to probe for an System-Binary * @returns System Binary path or undefined */ static async getSystemPath(systemBinary) { log('getSystempath'); try { await (0, utils_1.checkBinaryPermissions)(systemBinary); log(`getSystemPath: found system binary path at "${systemBinary}"`); return systemBinary; // returns if "access" is successful } catch (err) { log(`getSystemPath: can't find system binary at "${systemBinary}".\n${err instanceof Error ? err.message : err}`); } return undefined; } /** * Generate an "MongoBinaryPaths" object * * This Function should not hit the FileSystem * @returns an finished "MongoBinaryPaths" object */ static async generatePaths(opts) { log('generatePaths', opts); const final = { homeCache: '', modulesCache: '', relative: '', resolveConfig: '', }; const binaryName = await this.getBinaryName(opts); // Assign "node_modules/.cache" to modulesCache // if we're in postinstall script, npm will set the cwd too deep // when in postinstall, npm will provide an "INIT_CWD" env variable let nodeModulesDLDir = process.env['INIT_CWD'] || process.cwd(); // as long as "node_modules/mongodb-memory-server*" is included in the path, go the paths up while (nodeModulesDLDir.includes(`node_modules${path.sep}mongodb-memory-server`)) { nodeModulesDLDir = path.resolve(nodeModulesDLDir, '..', '..'); } const configPackagePath = (0, resolveConfig_1.packageJsonPath)(); // use the same "node_modules/.cache" as the package.json that was found for config options, if available if (configPackagePath && (await (0, utils_1.pathExists)(path.resolve(configPackagePath, 'node_modules')))) { nodeModulesDLDir = configPackagePath; } const tmpModulesCache = (0, find_cache_dir_1.default)({ name: 'mongodb-memory-server', cwd: nodeModulesDLDir, }); if (!(0, utils_1.isNullOrUndefined)(tmpModulesCache)) { final.modulesCache = this.combineBinaryName(path.resolve(tmpModulesCache), binaryName); } const homeCache = path.resolve(this.homedir(), '.cache/mongodb-binaries'); final.homeCache = this.combineBinaryName(homeCache, binaryName); // Resolve the config value "DOWNLOAD_DIR" if provided, otherwise remove from list const resolveConfigValue = opts.downloadDir || (0, resolveConfig_1.resolveConfig)(resolveConfig_1.ResolveConfigVariables.DOWNLOAD_DIR); if (!(0, utils_1.isNullOrUndefined)(resolveConfigValue) && resolveConfigValue.length > 0) { log(`generatePaths: resolveConfigValue is not empty`); final.resolveConfig = this.combineBinaryName(resolveConfigValue, binaryName); } // Resolve relative to cwd if no other has been found final.relative = this.combineBinaryName(path.resolve(process.cwd(), 'mongodb-binaries'), binaryName); return final; } /** * Generate the Path where an Binary will be located * @returns "boolean" indicating if the binary exists at the provided path, and "string" the path to use for the binary */ static async generateDownloadPath(opts) { const preferGlobal = (0, resolveConfig_1.envToBool)((0, resolveConfig_1.resolveConfig)(resolveConfig_1.ResolveConfigVariables.PREFER_GLOBAL_PATH)); log(`generateDownloadPath: Generating Download Path, preferGlobal: "${preferGlobal}"`); const paths = await this.generatePaths(opts); log('generateDownloadPath: Paths:', paths, opts.systemBinary); // SystemBinary will only be returned if defined and paths exists if (!!opts.systemBinary && (await (0, utils_1.pathExists)(opts.systemBinary))) { const sysPath = await this.getSystemPath(opts.systemBinary); if (!(0, utils_1.isNullOrUndefined)(sysPath)) { return [true, sysPath]; } } // Section where paths are probed for an existing binary if (await (0, utils_1.pathExists)(paths.resolveConfig)) { log(`generateDownloadPath: Found binary in resolveConfig (DOWNLOAD_DIR): "${paths.resolveConfig}"`); return [true, paths.resolveConfig]; } if (await (0, utils_1.pathExists)(paths.homeCache)) { log(`generateDownloadPath: Found binary in homeCache: "${paths.homeCache}"`); return [true, paths.homeCache]; } if (await (0, utils_1.pathExists)(paths.modulesCache)) { log(`generateDownloadPath: Found binary in modulesCache: "${paths.modulesCache}"`); return [true, paths.modulesCache]; } if (await (0, utils_1.pathExists)(paths.relative)) { log(`generateDownloadPath: Found binary in relative: "${paths.relative}"`); return [true, paths.relative]; } // Section where binary path gets generated when no binary was found log(`generateDownloadPath: no existing binary for version "${opts.version}" was found`); if (paths.resolveConfig.length > 0) { log(`generateDownloadPath: using resolveConfig (DOWNLOAD_DIR) "${paths.resolveConfig}"`); return [false, paths.resolveConfig]; } if (preferGlobal && !!paths.homeCache) { log(`generateDownloadPath: using global (preferGlobal) "${paths.homeCache}"`); return [false, paths.homeCache]; } // this case may not happen, if somehow the cwd gets changed outside of "node_modules" reach if (paths.modulesCache.length > 0) { log(`generateDownloadPath: using modulesCache "${paths.modulesCache}"`); return [false, paths.modulesCache]; } log(`generateDownloadPath: using relative "${paths.relative}"`); return [false, paths.relative]; } /** * This function is used, because jest just dosnt want "os.homedir" to be mocked * if someone can find an way to actually mock this in an test, please change it */ static homedir() { return (0, os_1.homedir)(); } } exports.DryMongoBinary = DryMongoBinary; //# sourceMappingURL=DryMongoBinary.js.map