feat(bin): symlink executables when directories.bin specified

Close #156
This commit is contained in:
zkochan
2017-02-21 23:29:30 +02:00
parent 8ef96ce838
commit 455b40fe87
6 changed files with 74 additions and 22 deletions

View File

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

View File

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

View File

@@ -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<Command[]> {
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<string[]> {
try {
return await fs.readdir(dir)
} catch (err) {
if ((<NodeJS.ErrnoException>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])
}))
}

View File

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

View File

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

View File

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