refactor(*.ts): use async/await

PR #357, Close #347
This commit is contained in:
zkochan
2016-09-12 00:58:15 +03:00
parent 356b8b27c4
commit 261d968876
18 changed files with 336 additions and 338 deletions

View File

@@ -60,18 +60,17 @@ export type BasicOptions = {
ignoreScripts: boolean
}
export default (opts: BasicOptions): Promise<CommandNamespace> => {
export default async function (opts: BasicOptions): Promise<CommandNamespace> {
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
}
}

View File

@@ -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[]) {

View File

@@ -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) {

View File

@@ -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 ||

View File

@@ -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 ((<NodeJS.ErrnoException>err).code !== 'EEXIST') return Promise.reject(err)
if ((<NodeJS.ErrnoException>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)
}
}

View File

@@ -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<PackageContext> {
export default async function install (ctx: InstallContext, pkgMeta: PackageMeta, modules: string, options: InstallationOptions): Promise<PackageContext> {
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<Package> {
async function saveCachedResolution (): Promise<Package> {
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 ((<NodeJS.ErrnoException>err).code !== 'ENOENT') throw err
return fn()
}
}
/*

View File

@@ -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 ((<NodeJS.ErrnoException>err).code !== 'ENOENT') throw err
return false
}
function verify (spec: PackageSpec, packageJson: Package) {
return packageJson.name === spec.name &&

View File

@@ -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) {

View File

@@ -18,11 +18,12 @@ function symlinkBundledDep (nodeModules: string, submod: string) {
return linkBins(nodeModules)
}
function isDir (path: string, fn: () => Promise<any>) {
return fs.stat(path)
.then((stat: Stats) => {
if (!stat.isDirectory()) return Promise.resolve()
async function isDir (path: string, fn: () => Promise<any>) {
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 ((<NodeJS.ErrnoException>err).code !== 'ENOENT') throw err
}
}

View File

@@ -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))
}))
}

View File

@@ -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 ((<NodeJS.ErrnoException>err).code !== 'ENOENT') throw err
}
}

View File

@@ -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) {

View File

@@ -29,7 +29,7 @@ export type GetFunc = (url: string, options?: GotOptions) => Promise<HttpRespons
export type Got = {
get: GetFunc,
getStream: (url: string, options?: GotOptions) => Promise<NodeJS.ReadableStream>,
getJSON: (url: string) => Promise<string>
getJSON<T>(url: string): Promise<T>
}
export default (opts: GotOptions): Got => {

View File

@@ -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]

View File

@@ -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
}
/**

View File

@@ -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<GitHubContentResponse>(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<GitHubRepoResponse>(url)
return body.sha
}
}

View File

@@ -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)

View File

@@ -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<ResolveResult> {
// { 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 = {