install-module-linked
Version:
Installs and symlinks a module into node_modules
50 lines (49 loc) • 2.34 kB
JavaScript
var _process_env_OSTYPE;
import fs from 'fs';
import { safeRm } from 'fs-remove-compat';
import mkdirp from 'mkdirp-classic';
import path from 'path';
import Queue from 'queue-cb';
import tempSuffix from 'temp-suffix';
import { DEFAULT_CACHE_PATH } from '../constants.js';
import cache from '../lib/cache.js';
import parseInstallString from '../lib/parseInstallString.js';
const isWindows = process.platform === 'win32' || /^(msys|cygwin)$/.test((_process_env_OSTYPE = process.env.OSTYPE) !== null && _process_env_OSTYPE !== void 0 ? _process_env_OSTYPE : '');
const symlinkType = isWindows ? 'junction' : 'dir';
export default function installModule(installString, nodeModulesPath, options, callback) {
const cachePath = options.cachePath || DEFAULT_CACHE_PATH;
const { name } = parseInstallString(installString);
const dest = path.join(nodeModulesPath, ...name.split('/'));
fs.stat(dest, (err)=>{
if (!err) return callback(undefined, dest); // already installed
cache(installString, cachePath, (err, cachedAt)=>{
if (err) {
console.log(`Could not install: ${installString}. Message: ${err.message}`);
return callback(err);
}
// Use temp symlink + atomic rename to avoid cross-process race conditions
const tempDest = tempSuffix(dest);
const queue = new Queue(1);
queue.defer((cb)=>mkdirp(path.dirname(dest), (err)=>cb(err)));
queue.defer((cb)=>fs.symlink(cachedAt, tempDest, symlinkType, (err)=>cb(err)));
queue.defer((cb)=>{
fs.rename(tempDest, dest, (err)=>{
var _err_code;
// If rename fails because dest exists, another process won - that's ok
if (err && [
'EEXIST',
'ENOTEMPTY',
'EPERM'
].indexOf((_err_code = err.code) !== null && _err_code !== void 0 ? _err_code : '') >= 0) {
safeRm(tempDest, ()=>cb());
return;
}
cb(err);
});
});
queue.await((err)=>{
err ? safeRm(tempDest, ()=>callback(err)) : callback(undefined, dest);
});
});
});
}