From 261d9688760f855435f9a60c5de4000ba52411dc Mon Sep 17 00:00:00 2001 From: zkochan Date: Mon, 12 Sep 2016 00:58:15 +0300 Subject: [PATCH] refactor(*.ts): use async/await PR #357, Close #347 --- src/api/init_cmd.ts | 47 ++++---- src/api/install.ts | 81 ++++++------- src/api/link.ts | 12 +- src/api/uninstall.ts | 51 ++++---- src/fs/force_symlink.ts | 20 ++-- src/install.ts | 193 +++++++++++++++---------------- src/install/is_available.ts | 20 ++-- src/install/link_bins.ts | 44 +++---- src/install/link_bundled_deps.ts | 13 ++- src/install/link_peers.ts | 26 ++--- src/install/post_install.ts | 40 ++++--- src/install_multiple.ts | 13 ++- src/network/got.ts | 2 +- src/remove_deps.ts | 4 +- src/resolve.ts | 5 +- src/resolve/github.ts | 55 +++++---- src/resolve/local.ts | 6 +- src/resolve/npm.ts | 42 +++---- 18 files changed, 336 insertions(+), 338 deletions(-) diff --git a/src/api/init_cmd.ts b/src/api/init_cmd.ts index 2c706bf339..9fd49b2a30 100644 --- a/src/api/init_cmd.ts +++ b/src/api/init_cmd.ts @@ -60,18 +60,17 @@ export type BasicOptions = { ignoreScripts: boolean } -export default (opts: BasicOptions): Promise => { +export default async function (opts: BasicOptions): Promise { const cwd = opts.cwd || process.cwd() const cmd: CommandNamespace = { ctx: {} } let lockfile: string - return (opts.global ? readGlobalPkg(opts.globalPath) : readPkgUp({ cwd })) - .then((_: PackageAndPath) => { cmd.pkg = _ }) - .then(() => updateContext()) - .then(() => mkdirp(cmd.ctx.store)) - .then(() => lock(lockfile)) - .then(() => cmd) + cmd.pkg = await (opts.global ? readGlobalPkg(opts.globalPath) : readPkgUp({ cwd })) + updateContext() + await mkdirp(cmd.ctx.store) + await lock(lockfile) + return cmd function updateContext () { const root = cmd.pkg.path ? path.dirname(cmd.pkg.path) : cwd @@ -109,25 +108,25 @@ function failIfNotCompatible (storeVersion: string) { } } -function readGlobalPkg (globalPath: string) { +async function readGlobalPkg (globalPath: string) { if (!globalPath) throw new Error('globalPath is required') const globalPnpm = resolveGlobalPkgPath(globalPath) const globalPkgPath = path.resolve(globalPnpm, 'package.json') - return readGlobalPkgJson(globalPkgPath) - .then(globalPkgJson => ({ - pkg: globalPkgJson, - path: globalPkgPath - })) -} - -function readGlobalPkgJson (globalPkgPath: string) { - try { - const globalPkgJson = requireJson(globalPkgPath) - return Promise.resolve(globalPkgJson) - } catch (err) { - const pkgJson = {} - return mkdirp(path.dirname(globalPkgPath)) - .then(_ => writeJson(globalPkgPath, pkgJson)) - .then(_ => pkgJson) + const globalPkgJson = await readGlobalPkgJson(globalPkgPath) + return { + pkg: globalPkgJson, + path: globalPkgPath + } +} + +async function readGlobalPkgJson (globalPkgPath: string) { + try { + const globalPkgJson = requireJson(globalPkgPath) + return globalPkgJson + } catch (err) { + const pkgJson = {} + await mkdirp(path.dirname(globalPkgPath)) + await writeJson(globalPkgPath, pkgJson) + return pkgJson } } diff --git a/src/api/install.ts b/src/api/install.ts index 9a95c2a8cf..54ea0ae47b 100644 --- a/src/api/install.ts +++ b/src/api/install.ts @@ -63,44 +63,42 @@ export type PublicInstallationOptions = BasicOptions & { * install({'lodash': '1.0.0', 'foo': '^2.1.0' }, { quiet: true }) */ -export default function install (fuzzyDeps: string[] | Dependencies, opts: PublicInstallationOptions) { +export default async function install (fuzzyDeps: string[] | Dependencies, opts: PublicInstallationOptions) { let packagesToInstall = mapify(fuzzyDeps) const installType = packagesToInstall && Object.keys(packagesToInstall).length ? 'named' : 'general' opts = Object.assign({}, defaults, opts) - let cmd: InstallNamespace const isProductionInstall = opts.production || process.env.NODE_ENV === 'production' + let cmd: InstallNamespace - return initCmd(opts) - .then(_ => { cmd = _ }) - .then(() => install()) - .then(() => linkPeers(cmd.ctx.store, cmd.ctx.installs)) + try { + cmd = await initCmd(opts) + await install() + await linkPeers(cmd.ctx.store, cmd.ctx.installs) // postinstall hooks - .then(() => { - if (opts.ignoreScripts || !cmd.ctx.piq || !cmd.ctx.piq.length) return - return seq( - cmd.ctx.piq.map(pkg => () => linkBins(path.join(pkg.path, '_', 'node_modules')) - .then(() => postInstall(pkg.path, installLogger(pkg.pkgFullname))) - .catch(err => { - if (cmd.ctx.installs[pkg.pkgFullname].optional) { - console.log('Skipping failed optional dependency ' + pkg.pkgFullname + ':') - console.log(err.message || err) - return - } - throw err - }) - ) - ) - }) - .then(() => linkBins(path.join(cmd.ctx.root, 'node_modules'))) - .then(() => mainPostInstall()) - .then(() => cmd.unlock()) - .catch((err: Error) => { + if (!(opts.ignoreScripts || !cmd.ctx.piq || !cmd.ctx.piq.length)) { + await seq( + cmd.ctx.piq.map(pkg => () => linkBins(path.join(pkg.path, '_', 'node_modules')) + .then(() => postInstall(pkg.path, installLogger(pkg.pkgFullname))) + .catch(err => { + if (cmd.ctx.installs[pkg.pkgFullname].optional) { + console.log('Skipping failed optional dependency ' + pkg.pkgFullname + ':') + console.log(err.message || err) + return + } + throw err + }) + )) + } + await linkBins(path.join(cmd.ctx.root, 'node_modules')) + await mainPostInstall() + await cmd.unlock() + } catch (err) { if (cmd && cmd.unlock) cmd.unlock() throw err - }) + } - function install () { + async function install () { if (installType !== 'named') { if (!cmd.pkg.pkg) throw runtimeError('No package.json found') packagesToInstall = Object.assign({}, cmd.pkg.pkg.dependencies || {}) @@ -114,18 +112,21 @@ export default function install (fuzzyDeps: string[] | Dependencies, opts: Publi fetchRetryMintimeout: opts.fetchRetryMintimeout, fetchRetryMaxtimeout: opts.fetchRetryMaxtimeout }) - return installMultiple(cmd.ctx, - packagesToInstall, - cmd.pkg.pkg && cmd.pkg.pkg.optionalDependencies, - path.join(cmd.ctx.root, 'node_modules'), - Object.assign({}, opts, { dependent: cmd.pkg && cmd.pkg.path }) - ) - .then(savePkgs) - .then(_ => cmd.storeJsonCtrl.save({ - pnpm: pnpmPkgJson.version, - dependents: cmd.ctx.dependents, - dependencies: cmd.ctx.dependencies - })) + + const pkgs = await installMultiple(cmd.ctx, + packagesToInstall, + cmd.pkg.pkg && cmd.pkg.pkg.optionalDependencies, + path.join(cmd.ctx.root, 'node_modules'), + Object.assign({}, opts, { dependent: cmd.pkg && cmd.pkg.path }) + ) + + await savePkgs(pkgs) + + cmd.storeJsonCtrl.save({ + pnpm: pnpmPkgJson.version, + dependents: cmd.ctx.dependents, + dependencies: cmd.ctx.dependencies + }) } function savePkgs (packages: PackageContext[]) { diff --git a/src/api/link.ts b/src/api/link.ts index 7388b6ab3b..48d5387208 100644 --- a/src/api/link.ts +++ b/src/api/link.ts @@ -7,15 +7,15 @@ import {linkPkgBins} from '../install/link_bins' import mkdirp from '../fs/mkdirp' import {PublicInstallationOptions} from './install' -export function linkFromRelative (linkTo: string, opts: PublicInstallationOptions) { +export async function linkFromRelative (linkTo: string, opts: PublicInstallationOptions) { const cwd = opts && opts.cwd || process.cwd() const linkedPkgPath = path.resolve(cwd, linkTo) const currentModules = path.resolve(cwd, 'node_modules') - return installPkgDeps(Object.assign({}, opts, { cwd: linkedPkgPath })) - .then(() => mkdirp(currentModules)) - .then(() => readPkgUp({ cwd: linkedPkgPath })) - .then(pkg => relSymlink(linkedPkgPath, path.resolve(currentModules, pkg.pkg.name))) - .then(() => linkPkgBins(currentModules, linkedPkgPath)) + await installPkgDeps(Object.assign({}, opts, { cwd: linkedPkgPath })) + await mkdirp(currentModules) + const pkg = await readPkgUp({ cwd: linkedPkgPath }) + await relSymlink(linkedPkgPath, path.resolve(currentModules, pkg.pkg.name)) + return linkPkgBins(currentModules, linkedPkgPath) } export function linkFromGlobal (pkgName: string, opts: PublicInstallationOptions) { diff --git a/src/api/uninstall.ts b/src/api/uninstall.ts index 76a2d72787..949cc175f3 100644 --- a/src/api/uninstall.ts +++ b/src/api/uninstall.ts @@ -9,41 +9,42 @@ import defaults from '../defaults' import requireJson from '../fs/require_json' import {PublicInstallationOptions} from './install' -export default function uninstallCmd (pkgsToUninstall: string[], opts: PublicInstallationOptions) { +export default async function uninstallCmd (pkgsToUninstall: string[], opts: PublicInstallationOptions) { opts = Object.assign({}, defaults, opts) - let cmd: CommandNamespace const uninstalledPkgs: string[] = [] const saveType = getSaveType(opts) + let cmd: CommandNamespace - return initCmd(opts) - .then(_ => { cmd = _ }) - .then(() => { - cmd.pkg.pkg.dependencies = cmd.pkg.pkg.dependencies || {} - const pkgFullNames = pkgsToUninstall.map(dep => cmd.ctx.dependencies[cmd.pkg.path].find(_ => _.indexOf(dep + '@') === 0)) - tryUninstall(pkgFullNames.slice()) - if (cmd.ctx.dependencies[cmd.pkg.path]) { - pkgFullNames.forEach(dep => { - cmd.ctx.dependencies[cmd.pkg.path].splice(cmd.ctx.dependencies[cmd.pkg.path].indexOf(dep), 1) - }) - if (!cmd.ctx.dependencies[cmd.pkg.path].length) { - delete cmd.ctx.dependencies[cmd.pkg.path] - } + try { + cmd = await initCmd(opts) + cmd.pkg.pkg.dependencies = cmd.pkg.pkg.dependencies || {} + const pkgFullNames = pkgsToUninstall.map(dep => cmd.ctx.dependencies[cmd.pkg.path].find(_ => _.indexOf(dep + '@') === 0)) + tryUninstall(pkgFullNames.slice()) + if (cmd.ctx.dependencies[cmd.pkg.path]) { + pkgFullNames.forEach(dep => { + cmd.ctx.dependencies[cmd.pkg.path].splice(cmd.ctx.dependencies[cmd.pkg.path].indexOf(dep), 1) + }) + if (!cmd.ctx.dependencies[cmd.pkg.path].length) { + delete cmd.ctx.dependencies[cmd.pkg.path] } - return Promise.all(uninstalledPkgs.map(removePkgFromStore)) - }) - .then(() => cmd.storeJsonCtrl.save({ + } + await Promise.all(uninstalledPkgs.map(removePkgFromStore)) + + cmd.storeJsonCtrl.save({ pnpm: cmd.ctx.pnpm, dependents: cmd.ctx.dependents, dependencies: cmd.ctx.dependencies - })) - .then(() => Promise.all(pkgsToUninstall.map(dep => rimraf(path.join(cmd.ctx.root, 'node_modules', dep))))) - .then(() => saveType && removeDeps(cmd.pkg.path, pkgsToUninstall, saveType)) - .then(() => cmd.unlock()) - .catch((err: Error) => { - if (cmd && cmd.unlock) cmd.unlock() - throw err }) + await Promise.all(pkgsToUninstall.map(dep => rimraf(path.join(cmd.ctx.root, 'node_modules', dep)))) + if (saveType) { + await removeDeps(cmd.pkg.path, pkgsToUninstall, saveType) + } + await cmd.unlock() + } catch (err) { + if (cmd && cmd.unlock) await cmd.unlock() + throw err + } function canBeUninstalled (pkgFullName: string) { return !cmd.ctx.dependents[pkgFullName] || !cmd.ctx.dependents[pkgFullName].length || diff --git a/src/fs/force_symlink.ts b/src/fs/force_symlink.ts index 76eca90f96..0b1e996bd9 100644 --- a/src/fs/force_symlink.ts +++ b/src/fs/force_symlink.ts @@ -9,21 +9,19 @@ export type SymlinkType = 'junction' | 'dir' * srcPath. API compatible with [`fs#symlink`](https://nodejs.org/api/fs.html#fs_fs_symlink_srcpath_dstpath_type_callback). */ -export default function forceSymlink (srcPath: string, dstPath: string, type: SymlinkType) { +export default async function forceSymlink (srcPath: string, dstPath: string, type: SymlinkType) { debug(`${srcPath} -> ${dstPath}`) try { fs.symlinkSync(srcPath, dstPath, type) - return Promise.resolve() + return } catch (err) { - if ((err).code !== 'EEXIST') return Promise.reject(err) + if ((err).code !== 'EEXIST') throw err - return fs.readlink(dstPath) - .then((linkString: string) => { - if (srcPath === linkString) { - return Promise.resolve() - } - return fs.unlink(dstPath) - .then(() => forceSymlink(srcPath, dstPath, type)) - }) + const linkString = await fs.readlink(dstPath) + if (srcPath === linkString) { + return + } + await fs.unlink(dstPath) + await forceSymlink(srcPath, dstPath, type) } } diff --git a/src/install.ts b/src/install.ts index b169507950..5cb5306477 100644 --- a/src/install.ts +++ b/src/install.ts @@ -2,6 +2,7 @@ import createDebug from './debug' const debug = createDebug('pnpm:install') import npa = require('npm-package-arg') import fs = require('mz/fs') +import {Stats} from 'fs' import logger = require('@zkochan/logger') import path = require('path') @@ -88,7 +89,7 @@ export type InstallLog = (msg: string, data?: any) => void * - symlink bins */ -export default function install (ctx: InstallContext, pkgMeta: PackageMeta, modules: string, options: InstallationOptions): Promise { +export default async function install (ctx: InstallContext, pkgMeta: PackageMeta, modules: string, options: InstallationOptions): Promise { debug('installing ' + pkgMeta.rawSpec) if (!ctx.builds) ctx.builds = {} if (!ctx.fetches) ctx.fetches = {} @@ -135,33 +136,35 @@ export default function install (ctx: InstallContext, pkgMeta: PackageMeta, modu const log: InstallLog = logger.fork(pkg.spec).log.bind(null, 'progress', pkgMeta.rawSpec) - // it might be a bundleDependency, in which case, don't bother - return isAvailable(pkg.spec, modules) - .then(_ => _ - ? saveCachedResolution() - .then(data => log('package.json', data)) - : resolve(Object.assign({}, pkg.spec, {root: options.parentRoot || ctx.root}), {log, got: ctx.got}) - .then(saveResolution) - .then(() => log('resolved', pkg)) - .then(() => buildToStoreCached(ctx, paths, pkg, log)) - .then(() => mkdirp(paths.modules)) - .then(() => symlinkToModules(join(paths.target, '_'), paths.modules)) - .then(() => log('package.json', requireJson(join(paths.target, '_', 'package.json'))))) - // done - .then(_ => { - if (!ctx.installs) ctx.installs = {} - if (!ctx.installs[pkg.fullname]) { - ctx.installs[pkg.fullname] = pkg - return - } + try { + // it might be a bundleDependency, in which case, don't bother + const available = await isAvailable(pkg.spec, modules) + if (available) { + const data = await saveCachedResolution() + log('package.json', data) + } else { + const res = await resolve(Object.assign({}, pkg.spec, {root: options.parentRoot || ctx.root}), {log, got: ctx.got}) + saveResolution(res) + log('resolved', pkg) + await buildToStoreCached(ctx, paths, pkg, log) + await mkdirp(paths.modules) + await symlinkToModules(join(paths.target, '_'), paths.modules) + log('package.json', requireJson(join(paths.target, '_', 'package.json'))) + } + + if (!ctx.installs) ctx.installs = {} + if (!ctx.installs[pkg.fullname]) { + ctx.installs[pkg.fullname] = pkg + } else { ctx.installs[pkg.fullname].optional = ctx.installs[pkg.fullname].optional && pkg.optional - }) - .then(_ => log('done')) - .then(_ => pkg) - .catch(err => { - log('error', err) - throw err - }) + } + + log('done') + return pkg + } catch (err) { + log('error', err) + throw err + } // set metadata as fetched from resolve() function saveResolution (res: ResolveResult) { @@ -173,17 +176,14 @@ export default function install (ctx: InstallContext, pkgMeta: PackageMeta, modu paths.target = join(ctx.store, res.fullname) } - function saveCachedResolution (): Promise { + async function saveCachedResolution (): Promise { const target = join(modules, pkg.spec.name) - return fs.lstat(target) - .then((stat: any) => { - if (stat.isSymbolicLink()) { - return fs.readlink(target) - .then((path: string) => save(abspath(path, target))) - } else { - return save(target) - } - }) + const stat: Stats = await fs.lstat(target) + if (stat.isSymbolicLink()) { + const path = await fs.readlink(target) + return save(abspath(path, target)) + } + return save(target) function save (fullpath: string): Package { const data = requireJson(join(fullpath, 'package.json')) @@ -209,12 +209,11 @@ function buildToStoreCached (ctx: InstallContext, paths: InstallationPaths, pkg: if (ctx.fetches[pkg.fullname]) return ctx.fetches[pkg.fullname] return make(paths.target, () => - memoize(ctx.builds, pkg.fullname, () => - Promise.resolve() - .then(_ => memoize(ctx.fetches, pkg.fullname, () => - fetchToStore(ctx, paths, pkg, log))) - .then(_ => buildInStore(ctx, paths, pkg, log)) - )) + memoize(ctx.builds, pkg.fullname, async function () { + await memoize(ctx.fetches, pkg.fullname, () => fetchToStore(ctx, paths, pkg, log)) + return buildInStore(ctx, paths, pkg, log) + }) + ) } /* @@ -222,53 +221,51 @@ function buildToStoreCached (ctx: InstallContext, paths: InstallationPaths, pkg: * Fetches from npm, recurses to dependencies, runs lifecycle scripts, etc */ -function fetchToStore (ctx: InstallContext, paths: InstallationPaths, pkg: PackageContext, log: InstallLog) { - return Promise.resolve() - // download and untar - .then(_ => log('download-queued')) - .then(_ => mkdirp(join(paths.target, '_'))) - .then(_ => fetch(join(paths.target, '_'), pkg.dist, {log, got: ctx.got})) - .then(_ => pkg.dist.local && pkg.dist.remove ? fs.unlink(pkg.dist.tarball) : Promise.resolve()) +async function fetchToStore (ctx: InstallContext, paths: InstallationPaths, pkg: PackageContext, log: InstallLog) { + // download and untar + log('download-queued') + await mkdirp(join(paths.target, '_')) + await fetch(join(paths.target, '_'), pkg.dist, {log, got: ctx.got}) + if (pkg.dist.local && pkg.dist.remove) { + await fs.unlink(pkg.dist.tarball) + } - // TODO: this is the point it becomes partially useable. - // ie, it can now be symlinked into .store/foo@1.0.0. - // it is only here that it should be available for ciruclar dependencies. + // TODO: this is the point it becomes partially useable. + // ie, it can now be symlinked into .store/foo@1.0.0. + // it is only here that it should be available for ciruclar dependencies. } -function buildInStore (ctx: InstallContext, paths: InstallationPaths, pkg: PackageContext, log: InstallLog) { +async function buildInStore (ctx: InstallContext, paths: InstallationPaths, pkg: PackageContext, log: InstallLog) { let fulldata: Package - return Promise.resolve() - .then(_ => { fulldata = requireJson(abspath(join(paths.target, '_', 'package.json'))) }) - .then(_ => log('package.json', fulldata)) + fulldata = requireJson(abspath(join(paths.target, '_', 'package.json'))) + log('package.json', fulldata) - .then(_ => linkBundledDeps(join(paths.target, '_'))) + await linkBundledDeps(join(paths.target, '_')) - // recurse down to dependencies - .then(_ => log('dependencies')) - .then(_ => installAll(ctx, - fulldata.dependencies, - fulldata.optionalDependencies, - join(paths.target, '_', 'node_modules'), - { - keypath: pkg.keypath.concat([ pkg.fullname ]), - dependent: pkg.fullname, - parentRoot: pkg.root, - optional: pkg.optional, - ignoreScripts: pkg.ignoreScripts - })) - - // symlink itself; . -> node_modules/lodash@4.0.0 - // this way it can require itself - .then(_ => symlinkSelf(paths.target, fulldata, pkg.keypath.length)) - - .then(_ => { - ctx.piq = ctx.piq || [] - ctx.piq.push({ - path: paths.target, - pkgFullname: pkg.fullname - }) + // recurse down to dependencies + log('dependencies') + await installAll(ctx, + fulldata.dependencies, + fulldata.optionalDependencies, + join(paths.target, '_', 'node_modules'), + { + keypath: pkg.keypath.concat([ pkg.fullname ]), + dependent: pkg.fullname, + parentRoot: pkg.root, + optional: pkg.optional, + ignoreScripts: pkg.ignoreScripts }) + + // symlink itself; . -> node_modules/lodash@4.0.0 + // this way it can require itself + await symlinkSelf(paths.target, fulldata, pkg.keypath.length) + + ctx.piq = ctx.piq || [] + ctx.piq.push({ + path: paths.target, + pkgFullname: pkg.fullname + }) } /* @@ -276,16 +273,15 @@ function buildInStore (ctx: InstallContext, paths: InstallationPaths, pkg: Packa * require('babel-runtime') within itself. */ -function symlinkSelf (target: string, pkg: Package, depth: number) { +async function symlinkSelf (target: string, pkg: Package, depth: number) { debug(`symlinkSelf ${pkg.name}`) if (depth === 0) { - return Promise.resolve() - } else { - return mkdirp(join(target, 'node_modules')) - .then(_ => relSymlink( - join('..', '_'), - join(target, 'node_modules', escapeName(pkg.name)))) + return } + await mkdirp(join(target, 'node_modules')) + await relSymlink( + join('..', '_'), + join(target, 'node_modules', escapeName(pkg.name))) } function escapeName (name: string) { @@ -300,7 +296,7 @@ function escapeName (name: string) { * symlinkToModules(fullname, modules) */ -function symlinkToModules (target: string, modules: string) { +async function symlinkToModules (target: string, modules: string) { // TODO: uncomment to make things fail const pkgData = requireJson(join(target, 'package.json')) if (!pkgData.name) { throw new Error('Invalid package.json for ' + target) } @@ -308,8 +304,8 @@ function symlinkToModules (target: string, modules: string) { // lodash -> .store/lodash@4.0.0 // .store/foo@1.0.0/node_modules/lodash -> ../../../.store/lodash@4.0.0 const out = join(modules, pkgData.name) - return mkdirp(dirname(out)) - .then(_ => relSymlink(target, out)) + await mkdirp(dirname(out)) + await relSymlink(target, out) } /* @@ -317,12 +313,13 @@ function symlinkToModules (target: string, modules: string) { * If it exists, don't do anything. */ -function make (path: string, fn: Function) { - return fs.stat(path) - .catch((err: NodeJS.ErrnoException) => { - if (err.code !== 'ENOENT') throw err - return fn() - }) +async function make (path: string, fn: Function) { + try { + await fs.stat(path) + } catch (err) { + if ((err).code !== 'ENOENT') throw err + return fn() + } } /* diff --git a/src/install/is_available.ts b/src/install/is_available.ts index 7d8399f273..a0a23c982c 100644 --- a/src/install/is_available.ts +++ b/src/install/is_available.ts @@ -15,20 +15,20 @@ import {Package} from '../api/init_cmd' * isAvailable(spec, 'path/to/node_modules') */ -export default function isAvailable (spec: PackageSpec, modules: string) { +export default async function isAvailable (spec: PackageSpec, modules: string) { const name = spec && spec.name - if (!name) return Promise.resolve(false) + if (!name) return false const packageJsonPath = path.join(modules, name, 'package.json') - return Promise.resolve() - .then(_ => fs.readFile(packageJsonPath)) - .then(_ => JSON.parse(_)) - .then(_ => verify(spec, _)) - .catch((err: NodeJS.ErrnoException) => { - if (err.code !== 'ENOENT') throw err - return false - }) + try { + const content = await fs.readFile(packageJsonPath) + const pkgJson = JSON.parse(content) + return verify(spec, pkgJson) + } catch (err) { + if ((err).code !== 'ENOENT') throw err + return false + } function verify (spec: PackageSpec, packageJson: Package) { return packageJson.name === spec.name && diff --git a/src/install/link_bins.ts b/src/install/link_bins.ts index e4aab47f6d..4bdf66dc79 100644 --- a/src/install/link_bins.ts +++ b/src/install/link_bins.ts @@ -52,38 +52,38 @@ function isScopedPkgsDir (dirPath: string) { * // node_modules/.bin/rimraf -> ../.store/rimraf@2.5.1/cmd.js */ -export function linkPkgBins (modules: string, target: string) { +export async function linkPkgBins (modules: string, target: string) { const pkg = tryRequire(path.join(target, 'package.json')) - if (!pkg || !pkg.bin) return Promise.resolve() + if (!pkg || !pkg.bin) return const bins = binify(pkg) const binDir = path.join(modules, '.bin') - return mkdirp(binDir) - .then(() => Promise.all(Object.keys(bins).map(bin => { - const actualBin = bins[bin] - const externalBinPath = path.join(binDir, bin) - - const targetPath = normalizePath(path.join(pkg.name, actualBin)) - if (isWindows) { - if (!preserveSymlinks) { - return cmdShim(externalBinPath, '../' + targetPath) - } - const proxyFilePath = path.join(binDir, bin + '.proxy') - fs.writeFileSync(proxyFilePath, 'require("../' + targetPath + '")', 'utf8') - return cmdShim(externalBinPath, path.relative(binDir, proxyFilePath)) - } + await mkdirp(binDir) + await Promise.all(Object.keys(bins).map(async function (bin) { + const actualBin = bins[bin] + const externalBinPath = path.join(binDir, bin) + const targetPath = normalizePath(path.join(pkg.name, actualBin)) + if (isWindows) { if (!preserveSymlinks) { - return makeExecutable(path.join(target, actualBin)) - .then(() => relSymlink( - path.join(target, actualBin), - externalBinPath)) + return cmdShim(externalBinPath, '../' + targetPath) } + const proxyFilePath = path.join(binDir, bin + '.proxy') + fs.writeFileSync(proxyFilePath, 'require("../' + targetPath + '")', 'utf8') + return cmdShim(externalBinPath, path.relative(binDir, proxyFilePath)) + } - return proxy(externalBinPath, targetPath) - }))) + if (!preserveSymlinks) { + await makeExecutable(path.join(target, actualBin)) + return relSymlink( + path.join(target, actualBin), + externalBinPath) + } + + return proxy(externalBinPath, targetPath) + })) } function makeExecutable (filePath: string) { diff --git a/src/install/link_bundled_deps.ts b/src/install/link_bundled_deps.ts index 8c5bffb781..b6169f38bb 100644 --- a/src/install/link_bundled_deps.ts +++ b/src/install/link_bundled_deps.ts @@ -18,11 +18,12 @@ function symlinkBundledDep (nodeModules: string, submod: string) { return linkBins(nodeModules) } -function isDir (path: string, fn: () => Promise) { - return fs.stat(path) - .then((stat: Stats) => { - if (!stat.isDirectory()) return Promise.resolve() +async function isDir (path: string, fn: () => Promise) { + try { + const stat = await fs.stat(path) + if (!stat.isDirectory()) return return fn() - }) - .catch((err: NodeJS.ErrnoException) => { if (err.code !== 'ENOENT') throw err }) + } catch (err) { + if ((err).code !== 'ENOENT') throw err + } } diff --git a/src/install/link_peers.ts b/src/install/link_peers.ts index 4f52d3a4eb..30bc6e6e6b 100644 --- a/src/install/link_peers.ts +++ b/src/install/link_peers.ts @@ -9,8 +9,8 @@ import {InstalledPackages} from '../api/install' * Links into `.store/node_modules` */ -export default function linkPeers (store: string, installs: InstalledPackages) { - if (!installs) return Promise.resolve() +export default async function linkPeers (store: string, installs: InstalledPackages) { + if (!installs) return const peers = {} const roots = {} @@ -32,15 +32,15 @@ export default function linkPeers (store: string, installs: InstalledPackages) { }) const modules = path.join(store, 'node_modules') - return mkdirp(modules) - .then(_ => Promise.all(Object.keys(roots).map(name => { - return unsymlink(path.join(modules, roots[name].name)) - }))) - .then(_ => Promise.all(Object.keys(peers).map(name => - unsymlink(path.join(modules, peers[name].spec.escapedName)) - .then(() => - relSymlink( - path.join(store, peers[name].fullname, '_'), - path.join(modules, peers[name].spec.escapedName))) - ))) + await mkdirp(modules) + await Promise.all(Object.keys(roots).map(name => { + return unsymlink(path.join(modules, roots[name].name)) + })) + + await Promise.all(Object.keys(peers).map(async function (name) { + await unsymlink(path.join(modules, peers[name].spec.escapedName)) + return relSymlink( + path.join(store, peers[name].fullname, '_'), + path.join(modules, peers[name].spec.escapedName)) + })) } diff --git a/src/install/post_install.ts b/src/install/post_install.ts index cbfcd41b99..ce5a5a6e56 100644 --- a/src/install/post_install.ts +++ b/src/install/post_install.ts @@ -5,23 +5,26 @@ import fs = require('mz/fs') import runScript from '../run_script' import requireJson from '../fs/require_json' -export default function postInstall (root_: string, log: Function) { +export default async function postInstall (root_: string, log: Function) { const root = path.join(root_, '_') const pkg = requireJson(path.join(root, 'package.json')) debug('postinstall', pkg.name + '@' + pkg.version) const scripts = pkg && pkg.scripts || {} - return Promise.resolve() - .then(_ => !scripts['install'] && checkBindingGyp(root, log)) - .then(_ => { - if (scripts['install']) { - return npmRunScript('install') - } - return npmRunScript('preinstall') - .then(_ => npmRunScript('postinstall')) - }) - function npmRunScript (scriptName: string) { - if (!scripts[scriptName]) return Promise.resolve() + if (!scripts['install']) { + await checkBindingGyp(root, log) + } + + if (scripts['install']) { + await npmRunScript('install') + return + } + await npmRunScript('preinstall') + await npmRunScript('postinstall') + return + + async function npmRunScript (scriptName: string) { + if (!scripts[scriptName]) return return runScript('npm', ['run', scriptName], { cwd: root, log }) } } @@ -31,10 +34,11 @@ export default function postInstall (root_: string, log: Function) { * `install` script (see `npm help scripts`). */ -function checkBindingGyp (root: string, log: Function) { - return fs.stat(path.join(root, 'binding.gyp')) - .then(() => runScript('node-gyp', ['rebuild'], { cwd: root, log })) - .catch((err: NodeJS.ErrnoException) => { - if (err.code !== 'ENOENT') throw err - }) +async function checkBindingGyp (root: string, log: Function) { + try { + await fs.stat(path.join(root, 'binding.gyp')) + await runScript('node-gyp', ['rebuild'], { cwd: root, log }) + } catch (err) { + if ((err).code !== 'ENOENT') throw err + } } diff --git a/src/install_multiple.ts b/src/install_multiple.ts index d7e52214d6..75f7e3964d 100644 --- a/src/install_multiple.ts +++ b/src/install_multiple.ts @@ -31,8 +31,9 @@ export default function installMultiple (ctx: InstallContext, requiredPkgsMap: D ctx.dependents = ctx.dependents || {} ctx.dependencies = ctx.dependencies || {} - return Promise.all(optionalPkgs.concat(requiredPkgs).map(pkg => install(ctx, pkg, modules, options) - .then((dependency: PackageContext) => { + return Promise.all(optionalPkgs.concat(requiredPkgs).map(async function (pkg) { + try { + const dependency = await install(ctx, pkg, modules, options) const depFullName = pkgFullName(dependency) ctx.dependents[depFullName] = ctx.dependents[depFullName] || [] if (ctx.dependents[depFullName].indexOf(options.dependent) === -1) { @@ -43,15 +44,15 @@ export default function installMultiple (ctx: InstallContext, requiredPkgsMap: D ctx.dependencies[options.dependent].push(depFullName) } return dependency - }) - .catch(err => { + } catch (err) { if (pkg.optional) { console.log('Skipping failed optional dependency ' + pkg.rawSpec + ':') console.log(err.message || err) - return + return null // is it OK to return null? } throw err - }))) + } + })) } function pkgMeta (name: string, version: string, optional: boolean) { diff --git a/src/network/got.ts b/src/network/got.ts index 5312ee51ef..a82150721b 100644 --- a/src/network/got.ts +++ b/src/network/got.ts @@ -29,7 +29,7 @@ export type GetFunc = (url: string, options?: GotOptions) => Promise Promise, - getJSON: (url: string) => Promise + getJSON(url: string): Promise } export default (opts: GotOptions): Got => { diff --git a/src/remove_deps.ts b/src/remove_deps.ts index 080ee1c79e..4e019428ce 100644 --- a/src/remove_deps.ts +++ b/src/remove_deps.ts @@ -2,10 +2,10 @@ import requireJson from './fs/require_json' import writeJson from './fs/write_json' import {DependenciesType} from './get_save_type' -export default (pkgJsonPath: string, removedPackages: string[], saveType: DependenciesType) => { +export default async function (pkgJsonPath: string, removedPackages: string[], saveType: DependenciesType) { const packageJson = requireJson(pkgJsonPath) packageJson[saveType] = packageJson[saveType] - if (!packageJson[saveType]) return Promise.resolve() + if (!packageJson[saveType]) return removedPackages.forEach(dependency => { delete packageJson[saveType][dependency] diff --git a/src/resolve.ts b/src/resolve.ts index a2a1478a56..a7a236f869 100644 --- a/src/resolve.ts +++ b/src/resolve.ts @@ -3,6 +3,7 @@ import resolveTarball from './resolve/tarball' import resolveGithub from './resolve/github' import resolveLocal from './resolve/local' import {PackageSpec} from './install' +import {Got} from './network/got' export type PackageDist = { local: boolean, @@ -16,7 +17,7 @@ export type ResolveResult = { fullname: string, version: string, dist: PackageDist, - root: string + root?: string } export type PackageToResolve = PackageSpec & { @@ -25,7 +26,7 @@ export type PackageToResolve = PackageSpec & { export type ResolveOptions = { log(msg: string): void, - got: any + got: Got } /** diff --git a/src/resolve/github.ts b/src/resolve/github.ts index 5ff0bd2b8d..0b04085ba8 100644 --- a/src/resolve/github.ts +++ b/src/resolve/github.ts @@ -8,52 +8,50 @@ import {Package} from '../api/init_cmd' const PARSE_GITHUB_RE = /^github:([^\/]+)\/([^#]+)(#(.+))?$/ -export default function resolveGithub (pkg: PackageToResolve, opts: ResolveOptions) { +export default async function resolveGithub (pkg: PackageToResolve, opts: ResolveOptions) { const getJSON = opts.got.getJSON const spec = parseGithubSpec(pkg) - return resolveRef(spec).then((ref: string) => { - spec.ref = ref - return resolvePackageJson(spec).then((pkg: Package) => ({ - name: pkg.name, - version: pkg.version, - fullname: pkgFullName({ - name: pkg.name, - version: ['github', spec.owner, spec.repo, spec.ref].join(delimiter) - }), - dist: { - tarball: [ - 'https://api.github.com/repos', - spec.owner, - spec.repo, - 'tarball', - spec.ref - ].join('/') - } - })) - }) + spec.ref = await resolveRef(spec) + const resPkg: Package = await resolvePackageJson(spec) + return { + name: resPkg.name, + version: resPkg.version, + fullname: pkgFullName({ + name: resPkg.name, + version: ['github', spec.owner, spec.repo, spec.ref].join(delimiter) + }), + dist: { + tarball: [ + 'https://api.github.com/repos', + spec.owner, + spec.repo, + 'tarball', + spec.ref + ].join('/') + } + } type GitHubContentResponse = { content: string } - function resolvePackageJson (spec: GitHubSpec) { + async function resolvePackageJson (spec: GitHubSpec) { const url = [ 'https://api.github.com/repos', spec.owner, spec.repo, 'contents/package.json?ref=' + spec.ref ].join('/') - return getJSON(url).then((body: GitHubContentResponse) => { - const content = new Buffer(body.content, 'base64').toString('utf8') - return JSON.parse(content) - }) + const body = await getJSON(url) + const content = new Buffer(body.content, 'base64').toString('utf8') + return JSON.parse(content) } type GitHubRepoResponse = { sha: string } - function resolveRef (spec: GitHubSpec) { + async function resolveRef (spec: GitHubSpec) { const url = [ 'https://api.github.com/repos', spec.owner, @@ -61,7 +59,8 @@ export default function resolveGithub (pkg: PackageToResolve, opts: ResolveOptio 'commits', spec.ref ].join('/') - return getJSON(url).then((body: GitHubRepoResponse) => body.sha) + const body = await getJSON(url) + return body.sha } } diff --git a/src/resolve/local.ts b/src/resolve/local.ts index be7be4813c..ed99c9ad54 100644 --- a/src/resolve/local.ts +++ b/src/resolve/local.ts @@ -9,12 +9,12 @@ import {PackageToResolve} from '../resolve' * Resolves a package hosted on the local filesystem */ -export default function resolveLocal (pkg: PackageToResolve) { +export default async function resolveLocal (pkg: PackageToResolve) { const dependencyPath = resolve(pkg.root, pkg.spec) if (dependencyPath.slice(-4) === '.tgz' || dependencyPath.slice(-7) === '.tar.gz') { const name = getTarballName(dependencyPath) - return Promise.resolve({ + return { name, fullname: pkgFullName({ name, @@ -29,7 +29,7 @@ export default function resolveLocal (pkg: PackageToResolve) { local: true, tarball: dependencyPath } - }) + } } return resolveFolder(dependencyPath) diff --git a/src/resolve/npm.ts b/src/resolve/npm.ts index a0b7015e1c..fdd9401c5e 100644 --- a/src/resolve/npm.ts +++ b/src/resolve/npm.ts @@ -3,7 +3,7 @@ const enc = encodeURIComponent import pkgFullName from '../pkg_full_name' import registryUrl = require('registry-url') import semver = require('semver') -import {PackageToResolve, ResolveOptions, PackageDist} from '../resolve' +import {PackageToResolve, ResolveOptions, PackageDist, ResolveResult} from '../resolve' import {Package} from '../api/init_cmd' /** @@ -20,30 +20,26 @@ import {Package} from '../api/init_cmd' * }) */ -export default function resolveNpm (pkg: PackageToResolve, opts: ResolveOptions) { +export default async function resolveNpm (pkg: PackageToResolve, opts: ResolveOptions): Promise { // { raw: 'rimraf@2', scope: null, name: 'rimraf', rawSpec: '2' || '' } - return Promise.resolve() - .then(_ => toUri(pkg)) - .then(url => { - if (opts.log) opts.log('resolving') - return opts.got.get(url) - }) - .then(res => JSON.parse(res.body)) - .then(res => pickVersionFromRegistryDocument(res, pkg)) - .then(res => ({ - name: res.name, - fullname: pkgFullName(res), - version: res.version, // used for displaying - dist: res.dist - })) - .catch((err: Error) => errify(err, pkg)) -} - -function errify (err: Error, pkg: PackageToResolve) { - if (err['statusCode'] === 404) { - throw new Error("Module '" + pkg.raw + "' not found") + try { + const url = toUri(pkg) + if (opts.log) opts.log('resolving') + const res = await opts.got.get(url) + const parsedBody = JSON.parse(res.body) + const correctPkg = pickVersionFromRegistryDocument(parsedBody, pkg) + return { + name: correctPkg.name, + fullname: pkgFullName(correctPkg), + version: correctPkg.version, // used for displaying + dist: correctPkg.dist + } + } catch (err) { + if (err['statusCode'] === 404) { + throw new Error("Module '" + pkg.raw + "' not found") + } + throw err } - throw err } type StringDict = {