mirror of
https://github.com/pnpm/pnpm.git
synced 2026-03-24 18:11:39 -04:00
feat(bin): symlink executables when directories.bin specified
Close #156
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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)))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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])
|
||||
}))
|
||||
}
|
||||
@@ -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})
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user