From 455b40fe87ab32aeffa97f74dfa54ffcbd99d30b Mon Sep 17 00:00:00 2001 From: zkochan Date: Tue, 21 Feb 2017 23:29:30 +0200 Subject: [PATCH] feat(bin): symlink executables when directories.bin specified Close #156 --- package.json | 2 +- src/api/uninstall.ts | 7 ++--- src/binify.ts | 60 +++++++++++++++++++++++++++++++++++------ src/install/linkBins.ts | 12 +++------ src/types.ts | 7 +++-- test/install.ts | 8 ++++++ 6 files changed, 74 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index e2854e8ac5..1522b0c0e9 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "ncp": "^2.0.0", "npm-run-all": "^4.0.1", "npm-scripts-info": "^0.3.6", - "pnpm-registry-mock": "^0.1.1", + "pnpm-registry-mock": "^0.2.3", "rimraf": "^2.5.4", "sepia": "^2.0.2", "tape": "^4.6.3", diff --git a/src/api/uninstall.ts b/src/api/uninstall.ts index a3e71dca1d..cf3ed724e9 100644 --- a/src/api/uninstall.ts +++ b/src/api/uninstall.ts @@ -119,10 +119,11 @@ function removeDependency (dependentPkgName: string, uninstalledPkg: string, gra } async function removeBins (uninstalledPkg: string, store: string, root: string) { - const uninstalledPkgJson = await readPkg(path.join(store, uninstalledPkg)) - const bins = binify(uninstalledPkgJson) + const uninstalledPkgPath = path.join(store, uninstalledPkg) + const uninstalledPkgJson = await readPkg(uninstalledPkgPath) + const cmds = await binify(uninstalledPkgJson, uninstalledPkgPath) return Promise.all( - Object.keys(bins).map(bin => rimraf(path.join(root, 'node_modules/.bin', bin))) + cmds.map(cmd => rimraf(path.join(root, 'node_modules', '.bin', cmd.name))) ) } diff --git a/src/binify.ts b/src/binify.ts index 0cebf73f60..99c761c88c 100644 --- a/src/binify.ts +++ b/src/binify.ts @@ -1,4 +1,13 @@ -import {Package} from './types' +import path = require('path') +import {Stats} from 'fs' +import fs = require('mz/fs') +import pFilter = require('p-filter') +import {Package, PackageBin} from './types' + +export type Command = { + name: string, + path: string, +} /** * Returns bins for a package in a standard object format. This normalizes @@ -11,12 +20,47 @@ import {Package} from './types' * binify({ name: 'rmrf', bin: { rmrf: 'cmd.js' } }) * => { rmrf: 'cmd.js' } */ -export default function binify (pkg: Package) { - if (typeof pkg.bin === 'string') { - const obj = {} - obj[pkg.name] = pkg.bin - return obj +export default async function binify (pkg: Package, pkgPath: string): Promise { + if (pkg.bin) { + return commandsFromBin(pkg.bin, pkg.name, pkgPath) } - - return pkg.bin || {} + if (pkg.directories && pkg.directories.bin) { + const binDir = path.join(pkgPath, pkg.directories.bin) + const files = await findFiles(binDir) + return pFilter( + files.map(file => ({ + name: file, + path: path.join(binDir, file) + })), + async (cmd: Command) => (await fs.stat(cmd.path)).isFile() + ) + } + return [] } + +async function findFiles (dir: string): Promise { + try { + return await fs.readdir(dir) + } catch (err) { + if ((err).code !== 'ENOENT') { + throw err + } + return [] + } +} + +function commandsFromBin (bin: PackageBin, pkgName: string, pkgPath: string) { + if (typeof bin === 'string') { + return [ + { + name: pkgName, + path: path.join(pkgPath, bin), + }, + ] + } + return Object.keys(bin) + .map(commandName => ({ + name: commandName, + path: path.join(pkgPath, bin[commandName]) + })) +} \ No newline at end of file diff --git a/src/install/linkBins.ts b/src/install/linkBins.ts index 7dd060cf08..532c7b7b76 100644 --- a/src/install/linkBins.ts +++ b/src/install/linkBins.ts @@ -35,18 +35,14 @@ export async function linkPkgBins (target: string, binPath: string) { return } - if (!pkg.bin) return - - const bins = binify(pkg) + const cmds = await binify(pkg, target) await mkdirp(binPath) - await Promise.all(Object.keys(bins).map(async function (bin) { - const externalBinPath = path.join(binPath, bin) - const actualBin = bins[bin] - const targetPath = path.join(target, actualBin) + await Promise.all(cmds.map(async cmd => { + const externalBinPath = path.join(binPath, cmd.name) const nodePath = (await getBinNodePaths(target)).join(path.delimiter) - return cmdShim(targetPath, externalBinPath, {nodePath}) + return cmdShim(cmd.path, externalBinPath, {nodePath}) })) } diff --git a/src/types.ts b/src/types.ts index cc994782ce..56c2645723 100644 --- a/src/types.ts +++ b/src/types.ts @@ -85,12 +85,15 @@ export type Dependencies = { [name: string]: string } +export type PackageBin = string | {[name: string]: string} + export type Package = { name: string, version: string, private?: boolean, - bin?: string | { - [name: string]: string + bin?: PackageBin, + directories?: { + bin?: string, }, dependencies?: Dependencies, devDependencies?: Dependencies, diff --git a/test/install.ts b/test/install.ts index 2892efe3c0..ee83d8f8a8 100644 --- a/test/install.ts +++ b/test/install.ts @@ -625,6 +625,14 @@ test('not top-level packages should find the plugins they use', async function ( t.equal(result.status, 0, 'standard exited with success') }) +test('bin specified in the directories property linked to .bin folder', async function (t) { + const project = prepare(t) + + await installPkgs(['pkg-with-directories-bin'], testDefaults()) + + await project.isExecutable('.bin/pkg-with-directories-bin') +}) + test('run js bin file', async function (t) { const project = prepare(t, { scripts: {