UNPKG

veendor

Version:

a tool for stroing your npm dependencies in arbitraty storage

286 lines (285 loc) 13.2 kB
'use strict'; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; result["default"] = mod; return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const logger_1 = require("../util/logger"); const helpers = __importStar(require("./helpers")); const pushBackends_1 = require("./pushBackends"); const rsyncWrapper = __importStar(require("../commandWrappers/rsyncWrapper")); const npmWrapper = __importStar(require("../commandWrappers/npmWrapper")); const gitWrapper = __importStar(require("../commandWrappers/gitWrapper")); const errors = __importStar(require("../errors")); const lodash_1 = __importDefault(require("lodash")); const objectDiff = __importStar(require("deep-object-diff")); const path_1 = __importDefault(require("path")); const fs_extra_1 = __importDefault(require("fs-extra")); const types_1 = require("../../types"); const hashGetters_1 = require("./hashGetters"); const progress_1 = require("../util/progress"); const { nodeModules, pkgJsonPath, originalCwd } = helpers.paths; // let clearNodeModulesPromise: Promise<void>; let isRsyncModeEnabled = false; var InstallStages; (function (InstallStages) { InstallStages[InstallStages["firstPull"] = 0] = "firstPull"; InstallStages[InstallStages["pullFromGitHistory"] = 1] = "pullFromGitHistory"; InstallStages[InstallStages["npmInstallDiff"] = 2] = "npmInstallDiff"; InstallStages[InstallStages["npmDedupe"] = 3] = "npmDedupe"; InstallStages[InstallStages["npmInstallAll"] = 4] = "npmInstallAll"; InstallStages[InstallStages["pushing"] = 5] = "pushing"; })(InstallStages || (InstallStages = {})); function install({ force = false, config, lockfilePath = null, rsyncMode = false }) { return __awaiter(this, void 0, void 0, function* () { const logger = logger_1.getLogger(); let backendsToPush = []; const [rsyncAvailable, nodeModulesInPlace] = yield Promise.all([ rsyncWrapper.rsyncAvailable(), nodeModulesAlreadyExist(), ]); isRsyncModeEnabled = rsyncMode && rsyncAvailable && nodeModulesInPlace; if (isRsyncModeEnabled) { logger.info('Working in rsync mode'); } const isGitRepo = yield gitWrapper.isGitRepo(originalCwd); if (nodeModulesInPlace) { if (!force) { throw new NodeModulesAlreadyExistError(); } if (!isRsyncModeEnabled) { logger.trace('Started removing node_modules'); clearNodeModules().then(() => { logger.trace('Successfully removed node_modules'); }, err => { logger.debug(`Error during node_modules removal: ${err.stack}`); }); } } /** * Calculating current hash */ let { hash, pkgJson } = yield hashGetters_1.getFSHash(config, pkgJsonPath, lockfilePath); logger.info(`Got hash:\t${hash}`); /** * Downloading deps */ let installStage = InstallStages.firstPull; let tryingHash = hash; let tryingPkgJson = pkgJson; let historyIndexStart = 0; while (true) { try { if (installStage === InstallStages.firstPull) { const info = yield pullBackends(tryingHash, config, lockfilePath); backendsToPush = info.missingBackends; installStage = InstallStages.pushing; break; } if (installStage === InstallStages.pullFromGitHistory) { yield pullBackends(tryingHash, config, lockfilePath); installStage = InstallStages.npmInstallDiff; continue; } if (installStage === InstallStages.npmInstallDiff) { yield installDiff(tryingPkgJson, pkgJson); backendsToPush = config.backends; if (config.dedupe) { installStage = InstallStages.npmDedupe; continue; } else { installStage = InstallStages.pushing; break; } } if (installStage === InstallStages.npmDedupe) { logger.info(`Running 'npm dedupe'`); yield npmWrapper.dedupe(); installStage = InstallStages.pushing; break; } if (installStage === InstallStages.npmInstallAll) { yield npmInstallAll(); backendsToPush = config.backends; installStage = InstallStages.pushing; break; } } catch (pullError) { if (pullError instanceof BundlesNotFoundError) { if (installStage === InstallStages.firstPull || installStage === InstallStages.pullFromGitHistory) { if (types_1.configHasHistory(config) && isGitRepo) { installStage = InstallStages.pullFromGitHistory; try { const res = yield hashGetters_1.getHistoryHash(config, lockfilePath, tryingHash, historyIndexStart); tryingHash = res.hash; tryingPkgJson = res.pkgJson; historyIndexStart = res.historyIndexEnd; continue; } catch (historyHashError) { if (historyHashError instanceof BundlesNotFoundError) { logger.trace(historyHashError); } } } if (!config.fallbackToNpm) { logger.error(`Couldn't find bundle with hash '${hash}'. 'fallbackToNpm' isn't set. Exiting`); throw pullError; } installStage = InstallStages.npmInstallAll; } } else { throw pullError; } } } /** * Pushing bundle */ try { yield pushBackends_1.pushBackends(backendsToPush, hash, false, config.clearSharedCache); } catch (pushError) { if (pushError instanceof errors.RePullNeeded) { // this happens if we failed to push bundle because someone got faster then us // in this case, we're gonna download bundle someone else has built // if true, catching BundleAlreadyExistsError from backend will reject result // just to make sure, we won't fall into infinite loop here yield pullBackends(hash, config, lockfilePath); } else { throw pushError; } } }); } exports.default = install; function nodeModulesAlreadyExist() { return __awaiter(this, void 0, void 0, function* () { const logger = logger_1.getLogger(); logger.trace('Checking node_modules'); try { yield fs_extra_1.default.access(nodeModules); logger.trace('\'node_modules\' directory already exists'); return true; } catch (err) { logger.trace('Node_modules not found'); return false; } }); } function clearNodeModules() { return __awaiter(this, void 0, void 0, function* () { const logger = logger_1.getLogger(); if (isRsyncModeEnabled) { return; } logger.trace(`moving node_modules to node_modules.bak.0`); let bodyCount = 0; let bakDirname; while (true) { bakDirname = `${nodeModules}.bak.${bodyCount}`; logger.trace(`moving node_modules to ${bakDirname}`); try { yield fs_extra_1.default.stat(bakDirname); logger.trace(`${bakDirname} already exists; incrementing`); bodyCount++; } catch (err) { if (err.code && err.code === 'ENOENT') { yield fs_extra_1.default.rename(nodeModules, bakDirname); logger.trace(`move was successful; removing ${bakDirname} without blocking`); return fs_extra_1.default.remove(bakDirname); } } } }); } function pullBackends(hash, config, lockfilePath, backendIndex = 0) { return __awaiter(this, void 0, void 0, function* () { const logger = logger_1.getLogger(); const backendConfig = config.backends[backendIndex]; if (!backendConfig) { throw new BundlesNotFoundError(`Backends don't have bundle ${hash}`); } logger.info(`Trying backend '${backendConfig.alias}' with hash ${hash}`); try { const cacheDirPath = yield helpers.createCleanCacheDir(backendConfig); if (isRsyncModeEnabled) { yield helpers.createCleanCwd(lockfilePath); } yield backendConfig.backend.pull(hash, backendConfig.options, cacheDirPath, progress_1.provideBackendCallTools(backendConfig, types_1.BackendCalls.push)); if (isRsyncModeEnabled) { logger.info(`Successfully fetched ${hash} from '${backendConfig.alias}'. Unpacking.`); const newNodeModules = path_1.default.resolve(process.cwd(), 'node_modules'); helpers.restoreCWD(); yield rsyncWrapper.syncDirs(newNodeModules, process.cwd()); } logger.info(`Pulled ${hash} from backend '${backendConfig.alias}'`); return { missingBackends: config.backends.slice(0, backendIndex) }; } catch (error) { helpers.restoreCWD(); if (error instanceof errors.BundleNotFoundError) { return pullBackends(hash, config, lockfilePath, backendIndex + 1); } else { logger.error(`Backend '${backendConfig.alias}' failed on pull:`); throw error; } } }); } function installDiff(oldPkgJson, newPkgJson) { return __awaiter(this, void 0, void 0, function* () { const logger = logger_1.getLogger(); const allDepsOld = Object.assign({}, oldPkgJson.devDependencies, oldPkgJson.dependencies); const allDepsNew = Object.assign({}, newPkgJson.devDependencies, newPkgJson.dependencies); const depsDiff = objectDiff.diff(allDepsOld, allDepsNew); const depsToInstall = lodash_1.default.omitBy(depsDiff, lodash_1.default.isUndefined); const depsToUninstall = lodash_1.default.keys(lodash_1.default.pickBy(depsDiff, lodash_1.default.isUndefined)); const loggingDepsToInstall = 'Installing dependencies: ' + Object.keys(depsToInstall).map(pkg => `${pkg}@${depsToInstall[pkg]}`).join(' '); const loggingDepsToUninstall = 'Uninstalling dependencies: ' + depsToUninstall.join(' '); if (lodash_1.default.keys(depsToInstall).length) { logger.info(loggingDepsToInstall); yield npmWrapper.install(depsToInstall); } if (depsToUninstall.length) { logger.info(loggingDepsToUninstall); yield npmWrapper.uninstall(depsToUninstall); } }); } function npmInstallAll() { const logger = logger_1.getLogger(); logger.info('Couldn\'t find bundles. Running npm install'); return npmWrapper.installAll(); } class PkgJsonNotFoundError extends errors.VeendorError { } exports.PkgJsonNotFoundError = PkgJsonNotFoundError; class NodeModulesAlreadyExistError extends errors.VeendorError { constructor() { super('NodeModulesAlreadyExistError'); } } exports.NodeModulesAlreadyExistError = NodeModulesAlreadyExistError; class BundlesNotFoundError extends errors.VeendorError { } exports.BundlesNotFoundError = BundlesNotFoundError;