veendor
Version:
a tool for stroing your npm dependencies in arbitraty storage
286 lines (285 loc) • 13.2 kB
JavaScript
;
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;