feat: use a shrinkwrap file instead of graph.yaml

Ref #658

BREAKING CHANGE: no `node_modules/.graph.yaml` used anymore
This commit is contained in:
zkochan
2017-03-12 22:49:43 +02:00
parent e2068c401e
commit d3b0bb0c75
19 changed files with 168 additions and 326 deletions

View File

@@ -86,16 +86,6 @@ Remove extraneous packages. Extraneous packages are packages that are not listed
* `options.production` - *Boolean* - by default `false`. If this property is `true`, prune will remove the packages specified in `devDependencies`.
* `options.cwd` - *String* - by default `process.cwd()`.
## `pnpm.prunePkgs(pkgs, [options])`
Remove extraneous packages specified in the `pkgs` arguments. Extraneous packages are packages that are not listed on the parent package's dependencies list.
**Arguments:**
* `pkgs` - *String[]* - prune only the specified packages.
* `options.production` - *Boolean* - by default `false`. If this property is `true`, prune will remove the packages specified in `devDependencies`.
* `options.cwd` - *String* - by default `process.cwd()`.
## `pnpm.verify([options])`
Return the list of modified dependencies.

View File

@@ -1,7 +1,7 @@
{
"name": "pnpm",
"description": "Fast, disk space efficient npm installs",
"version": "0.61.0",
"version": "0.62.0",
"author": "Rico Sta. Cruz <rico@ricostacruz.com>",
"bin": {
"pnpm": "lib/bin/pnpm.js"

View File

@@ -72,6 +72,13 @@ function check (pnpmVersion: string, storePath: string, modulesPath: string) {
if (semver.lt(pnpmVersion, '0.52.0')) {
throw new ModulesBreakingChangeError({ modulesPath, relatedPR: 593 })
}
if (semver.lt(pnpmVersion, '0.62.0')) {
throw new ModulesBreakingChangeError({
modulesPath,
relatedPR: 660,
additionalInformation: 'Information about the node_modules structure is stored in a node_modules/.shrinkwrap.yaml file instead of a node_modules/.graph.yaml file'
})
}
}
class UnexpectedStoreError extends PnpmError {

View File

@@ -4,12 +4,9 @@ import readPkg from '../fs/readPkg'
import writePkg = require('write-pkg')
import expandTilde, {isHomepath} from '../fs/expandTilde'
import {StrictPnpmOptions} from '../types'
import {
read as readGraph,
Graph,
} from '../fs/graphController'
import {
read as readShrinkwrap,
readPrivate as readPrivateShrinkwrap,
Shrinkwrap,
} from '../fs/shrinkwrap'
import {
@@ -26,7 +23,7 @@ export type PnpmContext = {
pkg?: Package,
storePath: string,
root: string,
graph: Graph,
privateShrinkwrap: Shrinkwrap,
shrinkwrap: Shrinkwrap,
isFirstInstallation: boolean,
}
@@ -55,14 +52,13 @@ export default async function getContext (opts: StrictPnpmOptions): Promise<Pnpm
}
}
const graph = await readGraph(path.join(root, 'node_modules')) || {}
const shrinkwrap = await readShrinkwrap(root, {force: opts.force})
const ctx: PnpmContext = {
pkg: pkg.pkg,
root,
storePath,
graph,
shrinkwrap,
privateShrinkwrap: await readPrivateShrinkwrap(root),
isFirstInstallation,
}

View File

@@ -1,11 +1,9 @@
import rimraf = require('rimraf-then')
import path = require('path')
import RegClient = require('npm-registry-client')
import logger from 'pnpm-logger'
import globalBinPath = require('global-bin-path')
import pLimit = require('p-limit')
import npa = require('npm-package-arg')
import R = require('ramda')
import {PnpmOptions, StrictPnpmOptions, Dependencies} from '../types'
import createGot from '../network/got'
import getContext, {PnpmContext} from './getContext'
@@ -18,15 +16,14 @@ import postInstall from '../install/postInstall'
import extendOptions from './extendOptions'
import pnpmPkgJson from '../pnpmPkgJson'
import lock from './lock'
import {save as saveGraph, Graph} from '../fs/graphController'
import {read as readStore, save as saveStore} from '../fs/storeController'
import {
save as saveShrinkwrap,
prune as pruneShrinkwrap,
Shrinkwrap,
ResolvedDependencies,
} from '../fs/shrinkwrap'
import {save as saveModules} from '../fs/modulesController'
import {tryUninstall, removePkgFromStore} from './uninstall'
import removeOrphanPkgs from './removeOrphanPkgs'
import mkdirp = require('mkdirp-promise')
import createMemoize, {MemoizedFunc} from '../memoize'
import linkBins from '../install/linkBins'
@@ -41,7 +38,6 @@ export type InstalledPackages = {
export type InstallContext = {
installs: InstalledPackages,
installationSequence: string[],
graph: Graph,
shrinkwrap: Shrinkwrap,
installed: Set<string>,
fetchingLocker: MemoizedFunc<Boolean>,
@@ -51,7 +47,7 @@ export type InstallContext = {
export async function install (maybeOpts?: PnpmOptions) {
const opts = extendOptions(maybeOpts)
const ctx = await getContext(opts)
const installCtx = await createInstallCmd(opts, ctx.graph, ctx.shrinkwrap)
const installCtx = await createInstallCmd(opts, ctx.shrinkwrap)
if (!ctx.pkg) throw new Error('No package.json found')
const optionalDeps = ctx.pkg.optionalDependencies || {}
@@ -86,7 +82,7 @@ export async function installPkgs (fuzzyDeps: string[] | Dependencies, maybeOpts
throw new Error('At least one package has to be installed')
}
const ctx = await getContext(opts)
const installCtx = await createInstallCmd(opts, ctx.graph, ctx.shrinkwrap)
const installCtx = await createInstallCmd(opts, ctx.shrinkwrap)
return lock(
ctx.storePath,
@@ -127,8 +123,6 @@ async function installInContext (
installCtx: InstallContext,
opts: StrictPnpmOptions
) {
// TODO: ctx.graph should not be muted. installMultiple should return a new graph
const oldGraph: Graph = R.clone(ctx.graph)
const nodeModulesPath = path.join(ctx.root, 'node_modules')
const client = new RegClient(adaptConfig(opts))
@@ -198,10 +192,9 @@ async function installInContext (
})
}
const newGraph = Object.assign({}, ctx.graph)
await removeOrphanPkgs(oldGraph, newGraph, ctx.root, ctx.storePath)
await saveGraph(path.join(ctx.root, 'node_modules'), newGraph)
await saveShrinkwrap(ctx.root, ctx.shrinkwrap)
const newShr = pruneShrinkwrap(ctx.shrinkwrap)
await removeOrphanPkgs(ctx.privateShrinkwrap, newShr, ctx.root, ctx.storePath)
await saveShrinkwrap(ctx.root, newShr)
if (ctx.isFirstInstallation) {
await saveModules(path.join(ctx.root, 'node_modules'), {
packageManager: `${pnpmPkgJson.name}@${pnpmPkgJson.version}`,
@@ -243,47 +236,9 @@ async function installInContext (
}
}
async function removeOrphanPkgs (oldGraphJson: Graph, newGraphJson: Graph, root: string, storePath: string) {
const oldPkgIds = new Set(Object.keys(oldGraphJson))
const newPkgIds = new Set(Object.keys(newGraphJson))
const store = await readStore(storePath) || {}
const notDependents = difference(oldPkgIds, newPkgIds)
await Promise.all(Array.from(notDependents).map(async function (notDependent) {
if (store[notDependent]) {
store[notDependent].splice(store[notDependent].indexOf(root), 1)
if (!store[notDependent].length) {
delete store[notDependent]
await rimraf(path.join(storePath, notDependent))
}
}
}))
const newDependents = difference(newPkgIds, oldPkgIds)
newDependents.forEach(newDependent => {
store[newDependent] = store[newDependent] || []
if (store[newDependent].indexOf(root) === -1) {
store[newDependent].push(root)
}
})
await saveStore(storePath, store)
}
function difference<T> (setA: Set<T>, setB: Set<T>) {
const difference = new Set(setA)
for (const elem of setB) {
difference.delete(elem)
}
return difference
}
async function createInstallCmd (opts: StrictPnpmOptions, graph: Graph, shrinkwrap: Shrinkwrap): Promise<InstallContext> {
async function createInstallCmd (opts: StrictPnpmOptions, shrinkwrap: Shrinkwrap): Promise<InstallContext> {
return {
installs: {},
graph,
shrinkwrap,
installed: new Set(),
installationSequence: [],

View File

@@ -1,11 +1,18 @@
import path = require('path')
import R = require('ramda')
import getContext from './getContext'
import {PnpmOptions, Package} from '../types'
import extendOptions from './extendOptions'
import {uninstallInContext} from './uninstall'
import getPkgDirs from '../fs/getPkgDirs'
import readPkg from '../fs/readPkg'
import lock from './lock'
import removeOrphanPkgs from './removeOrphanPkgs'
import npa = require('npm-package-arg')
import {PackageSpec} from '../resolve'
import {
ResolvedDependencies,
prune as pruneShrinkwrap,
} from '../fs/shrinkwrap'
export async function prune(maybeOpts?: PnpmOptions): Promise<void> {
const opts = extendOptions(maybeOpts)
@@ -21,32 +28,15 @@ export async function prune(maybeOpts?: PnpmOptions): Promise<void> {
const extraneousPkgs = await getExtraneousPkgs(pkg, ctx.root, opts.production)
await uninstallInContext(extraneousPkgs, ctx.pkg, ctx, opts)
},
{stale: opts.lockStaleDuration})
}
const newShr = ctx.shrinkwrap
newShr.dependencies = <ResolvedDependencies>R.pickBy((value, key) => {
const spec: PackageSpec = npa(key)
return extraneousPkgs.indexOf(spec.name) === -1
}, newShr.dependencies)
export async function prunePkgs(pkgsToPrune: string[], maybeOpts?: PnpmOptions): Promise<void> {
const opts = extendOptions(maybeOpts)
const prunedShr = pruneShrinkwrap(newShr)
const ctx = await getContext(opts)
return lock(ctx.storePath, async function () {
if (!ctx.pkg) {
throw new Error('No package.json found - cannot prune')
}
const pkg = ctx.pkg
const extraneousPkgs = await getExtraneousPkgs(pkg, ctx.root, opts.production)
const notPrunable = pkgsToPrune.filter(pkgToPrune => extraneousPkgs.indexOf(pkgToPrune) === -1)
if (notPrunable.length) {
const err = new Error(`Unable to prune ${notPrunable.join(', ')} because it is not an extraneous package`)
err['code'] = 'PRUNE_NOT_EXTR'
throw err
}
await uninstallInContext(pkgsToPrune, ctx.pkg, ctx, opts)
await removeOrphanPkgs(ctx.privateShrinkwrap, prunedShr, ctx.root, ctx.storePath)
},
{stale: opts.lockStaleDuration})
}

View File

@@ -0,0 +1,64 @@
import rimraf = require('rimraf-then')
import path = require('path')
import {Shrinkwrap} from '../fs/shrinkwrap'
import {read as readStore, save as saveStore} from '../fs/storeController'
import binify from '../binify'
import safeReadPkg from '../fs/safeReadPkg'
import R = require('ramda')
import npa = require('npm-package-arg')
import {PackageSpec} from '../resolve'
export default async function removeOrphanPkgs (
oldShr: Shrinkwrap,
newShr: Shrinkwrap,
root: string,
storePath: string
) {
const oldPkgNames = Object.keys(oldShr.dependencies).map(npa).map((spec: PackageSpec) => spec.name)
const newPkgNames = Object.keys(newShr.dependencies).map(npa).map((spec: PackageSpec) => spec.name)
const removedTopDeps = R.difference(oldPkgNames, newPkgNames)
await Promise.all(removedTopDeps.map(depName => Promise.all([
rimraf(path.join(root, 'node_modules', depName)),
removeBins(depName, root),
])))
const oldPkgIds = Object.keys(oldShr.packages)
const newPkgIds = Object.keys(newShr.packages)
const store = await readStore(storePath) || {}
const notDependents = R.difference(oldPkgIds, newPkgIds)
await Promise.all(Array.from(notDependents).map(async notDependent => {
if (store[notDependent]) {
store[notDependent].splice(store[notDependent].indexOf(root), 1)
if (!store[notDependent].length) {
delete store[notDependent]
await rimraf(path.join(storePath, notDependent))
}
}
await rimraf(path.join(root, 'node_modules', `.${notDependent}`))
}))
const newDependents = R.difference(newPkgIds, oldPkgIds)
newDependents.forEach(newDependent => {
store[newDependent] = store[newDependent] || []
if (store[newDependent].indexOf(root) === -1) {
store[newDependent].push(root)
}
})
await saveStore(storePath, store)
}
async function removeBins (uninstalledPkg: string, root: string) {
const uninstalledPkgPath = path.join(root, 'node_modules', uninstalledPkg)
const uninstalledPkgJson = await safeReadPkg(uninstalledPkgPath)
if (!uninstalledPkgJson) return
const cmds = await binify(uninstalledPkgJson, uninstalledPkgPath)
return Promise.all(
cmds.map(cmd => rimraf(path.join(root, 'node_modules', '.bin', cmd.name)))
)
}

View File

@@ -8,8 +8,14 @@ import extendOptions from './extendOptions'
import readPkg from '../fs/readPkg'
import {PnpmOptions, StrictPnpmOptions, Package} from '../types'
import lock from './lock'
import {save as saveGraph, Graph, GRAPH_ENTRY} from '../fs/graphController'
import {save as saveShrinkwrap} from '../fs/shrinkwrap'
import {
Shrinkwrap,
save as saveShrinkwrap,
prune as pruneShrinkwrap,
} from '../fs/shrinkwrap'
import removeOrphanPkgs from './removeOrphanPkgs'
import npa = require('npm-package-arg')
import {PackageSpec} from '../resolve'
export default async function uninstallCmd (pkgsToUninstall: string[], maybeOpts?: PnpmOptions) {
const opts = extendOptions(maybeOpts)
@@ -29,47 +35,19 @@ export default async function uninstallCmd (pkgsToUninstall: string[], maybeOpts
}
export async function uninstallInContext (pkgsToUninstall: string[], pkg: Package, ctx: PnpmContext, opts: StrictPnpmOptions) {
pkg.dependencies = pkg.dependencies || {}
// for backward compatibility
const entry = ctx.graph[ctx.root] ? ctx.root : GRAPH_ENTRY
// this is OK. The store might not have records for the package
// maybe it was cloned, `pnpm install` was not executed
// and remove is done on a package with no dependencies installed
ctx.graph[entry] = ctx.graph[entry] || {}
ctx.graph[entry].dependencies = ctx.graph[entry].dependencies || {}
const pkgIds = <string[]>pkgsToUninstall
.map(dep => ctx.graph[entry].dependencies[dep])
.filter(pkgId => !!pkgId)
const uninstalledPkgs = tryUninstall(pkgIds.slice(), ctx.graph, entry)
await Promise.all(
uninstalledPkgs.map(uninstalledPkg => removeBins(uninstalledPkg, ctx.storePath, ctx.root))
)
if (ctx.graph[entry].dependencies) {
pkgsToUninstall.forEach(dep => {
delete ctx.graph[entry].dependencies[dep]
})
if (!Object.keys(ctx.graph[entry].dependencies).length) {
delete ctx.graph[entry].dependencies
}
}
await Promise.all(uninstalledPkgs.map(pkgId => removePkgFromStore(pkgId, ctx.storePath)))
await saveGraph(path.join(ctx.root, 'node_modules'), ctx.graph)
await Promise.all(pkgsToUninstall.map(dep => rimraf(path.join(ctx.root, 'node_modules', dep))))
const saveType = getSaveType(opts)
if (saveType) {
const pkgJsonPath = path.join(ctx.root, 'package.json')
const pkg = await removeDeps(pkgJsonPath, pkgsToUninstall, saveType)
for (let depName in ctx.shrinkwrap.dependencies) {
if (!isDependentOn(pkg, depName)) {
delete ctx.shrinkwrap.dependencies[depName]
for (let depSpecRaw in ctx.shrinkwrap.dependencies) {
const depSpec: PackageSpec = npa(depSpecRaw)
if (!isDependentOn(pkg, depSpec.name)) {
delete ctx.shrinkwrap.dependencies[depSpecRaw]
}
}
await saveShrinkwrap(ctx.root, ctx.shrinkwrap)
const newShr = await pruneShrinkwrap(ctx.shrinkwrap)
await removeOrphanPkgs(ctx.privateShrinkwrap, newShr, ctx.root, ctx.storePath)
await saveShrinkwrap(ctx.root, newShr)
}
}
@@ -81,52 +59,3 @@ function isDependentOn (pkg: Package, depName: string): boolean {
]
.some(deptype => pkg[deptype] && pkg[deptype][depName])
}
function canBeUninstalled (pkgId: string, graph: Graph, pkgPath: string) {
return !graph[pkgId] || !graph[pkgId].dependents || !graph[pkgId].dependents.length ||
graph[pkgId].dependents.length === 1 && graph[pkgId].dependents.indexOf(pkgPath) !== -1
}
export function tryUninstall (pkgIds: string[], graph: Graph, pkgPath: string) {
const uninstalledPkgs: string[] = []
let numberOfUninstalls: number
do {
numberOfUninstalls = 0
for (let i = 0; i < pkgIds.length; ) {
if (canBeUninstalled(pkgIds[i], graph, pkgPath)) {
const uninstalledPkg = pkgIds.splice(i, 1)[0]
uninstalledPkgs.push(uninstalledPkg)
const deps = graph[uninstalledPkg] && graph[uninstalledPkg].dependencies || {}
const depIds = Object.keys(deps).map(depName => deps[depName])
delete graph[uninstalledPkg]
depIds.forEach((dep: string) => removeDependency(dep, uninstalledPkg, graph))
Array.prototype.push.apply(uninstalledPkgs, tryUninstall(depIds, graph, uninstalledPkg))
numberOfUninstalls++
continue
}
i++
}
} while (numberOfUninstalls)
return uninstalledPkgs
}
function removeDependency (dependentPkgName: string, uninstalledPkg: string, graph: Graph) {
if (!graph[dependentPkgName].dependents) return
graph[dependentPkgName].dependents.splice(graph[dependentPkgName].dependents.indexOf(uninstalledPkg), 1)
if (!graph[dependentPkgName].dependents.length) {
delete graph[dependentPkgName].dependents
}
}
async function removeBins (uninstalledPkg: string, store: string, root: string) {
const uninstalledPkgPath = path.join(store, uninstalledPkg)
const uninstalledPkgJson = await readPkg(uninstalledPkgPath)
const cmds = await binify(uninstalledPkgJson, uninstalledPkgPath)
return Promise.all(
cmds.map(cmd => rimraf(path.join(root, 'node_modules', '.bin', cmd.name)))
)
}
export function removePkgFromStore (pkgId: string, store: string) {
return rimraf(path.join(store, pkgId))
}

View File

@@ -4,23 +4,14 @@ import {PnpmOptions} from '../types'
import extendOptions from './extendOptions'
import getContext from './getContext'
import untouched from '../pkgIsUntouched'
import {GRAPH_ENTRY} from '../fs/graphController'
export default async function (maybeOpts: PnpmOptions) {
const opts = extendOptions(maybeOpts)
const ctx = await getContext(opts)
if (!ctx.graph) return []
if (!ctx.shrinkwrap) return []
const pkgPaths = Object.keys(ctx.graph)
.filter(pkgPath => !isProjectPath(pkgPath))
const pkgPaths = Object.keys(ctx.shrinkwrap.packages || {})
.map(pkgPath => path.join(ctx.storePath, pkgPath))
return await pFilter(pkgPaths, async (pkgPath: string) => !await untouched(pkgPath))
}
function isProjectPath (pkgPath: string) {
return pkgPath === GRAPH_ENTRY ||
// next are for backward compatibility
// previous versions of .graph.yaml had the package path as the entry point
pkgPath.startsWith('/') || pkgPath[1] === ':'
}

View File

@@ -1,9 +1,6 @@
import {prune, prunePkgs} from '../api/prune'
import {prune} from '../api/prune'
import {PnpmOptions} from '../types'
export default (input: string[], opts: PnpmOptions) => {
if (input && input.length) {
return prunePkgs(input, opts)
}
return prune(opts)
}

View File

@@ -1,37 +0,0 @@
import path = require('path')
import loadYamlFile = require('load-yaml-file')
import writeYamlFile = require('write-yaml-file')
const graphFileName = '.graph.yaml'
export const GRAPH_ENTRY = '..'
export type Graph = {
[name: string]: PackageGraph
}
export type PackageGraph = {
dependents: string[],
dependencies: DependenciesResolution
}
export type DependenciesResolution = {
[name: string]: string
}
export async function read (modulesPath: string): Promise<Graph | null> {
const graphYamlPath = path.join(modulesPath, graphFileName)
try {
return await loadYamlFile<Graph>(graphYamlPath)
} catch (err) {
if ((<NodeJS.ErrnoException>err).code !== 'ENOENT') {
throw err
}
return null
}
}
export function save (modulesPath: string, graph: Graph) {
const graphYamlPath = path.join(modulesPath, graphFileName)
return writeYamlFile(graphYamlPath, graph, {sortKeys: true})
}

11
src/fs/safeReadPkg.ts Normal file
View File

@@ -0,0 +1,11 @@
import {Package} from '../types'
import readPkg from './readPkg'
export default async function safeReadPkg (pkgPath: string): Promise<Package | null> {
try {
return await readPkg(pkgPath)
} catch (err) {
if ((<NodeJS.ErrnoException>err).code !== 'ENOENT') throw err
return null
}
}

View File

@@ -11,6 +11,7 @@ import isCI = require('is-ci')
const shrinkwrapLogger = logger('shrinkwrap')
const SHRINKWRAP_FILENAME = 'shrinkwrap.yaml'
const PRIVATE_SHRINKWRAP_FILENAME = path.join('node_modules', '.shrinkwrap.yaml')
const SHRINKWRAP_VERSION = 1
function getDefaultShrinkwrap () {
@@ -45,6 +46,23 @@ export type ResolvedDependencies = {
[pkgName: string]: string,
}
export async function readPrivate (pkgPath: string): Promise<Shrinkwrap> {
const shrinkwrapPath = path.join(pkgPath, PRIVATE_SHRINKWRAP_FILENAME)
let shrinkwrap
try {
shrinkwrap = await loadYamlFile<Shrinkwrap>(shrinkwrapPath)
} catch (err) {
if ((<NodeJS.ErrnoException>err).code !== 'ENOENT') {
throw err
}
return getDefaultShrinkwrap()
}
if (shrinkwrap && shrinkwrap.version === SHRINKWRAP_VERSION) {
return shrinkwrap
}
throw new ShrinkwrapBreakingChangeError(shrinkwrapPath)
}
export async function read (pkgPath: string, opts: {force: boolean}): Promise<Shrinkwrap> {
const shrinkwrapPath = path.join(pkgPath, SHRINKWRAP_FILENAME)
let shrinkwrap
@@ -68,22 +86,29 @@ export async function read (pkgPath: string, opts: {force: boolean}): Promise<Sh
export function save (pkgPath: string, shrinkwrap: Shrinkwrap) {
const shrinkwrapPath = path.join(pkgPath, SHRINKWRAP_FILENAME)
const privateShrinkwrapPath = path.join(pkgPath, PRIVATE_SHRINKWRAP_FILENAME)
// empty shrinkwrap is not saved
if (Object.keys(shrinkwrap.dependencies).length === 0) {
return rimraf(shrinkwrapPath)
return Promise.all([
rimraf(shrinkwrapPath),
rimraf(privateShrinkwrapPath),
])
}
const prunedShr = prune(shrinkwrap)
return writeYamlFile(shrinkwrapPath, prunedShr, {
const formatOpts = {
sortKeys: true,
lineWidth: 1000,
noCompatMode: true,
})
}
return Promise.all([
writeYamlFile(shrinkwrapPath, shrinkwrap, formatOpts),
writeYamlFile(privateShrinkwrapPath, shrinkwrap, formatOpts),
])
}
function prune (shr: Shrinkwrap): Shrinkwrap {
export function prune (shr: Shrinkwrap): Shrinkwrap {
return {
version: SHRINKWRAP_VERSION,
dependencies: shr.dependencies,

View File

@@ -12,7 +12,6 @@ import installChecks = require('pnpm-install-checks')
import pnpmPkg from '../pnpmPkgJson'
import symlinkDir from 'symlink-dir'
import exists = require('path-exists')
import {Graph, GRAPH_ENTRY} from '../fs/graphController'
import logStatus from '../logging/logInstallStatus'
import rimraf = require('rimraf-then')
import fs = require('mz/fs')
@@ -101,8 +100,6 @@ async function installMultiple (
offline: boolean,
}
): Promise<InstalledPackage[]> {
ctx.graph = ctx.graph || {}
const nonLinkedPkgs = modules === options.baseNodeModules
// only check modules on the first level
? await pFilter(specs, (spec: PackageSpec) => !spec.name || isInnerLink(modules, spec.name))
@@ -227,8 +224,6 @@ async function install (
addInstalledPkg(ctx.installs, dependency)
addToGraph(ctx.graph, dependentId, dependency)
const linking = ctx.linkingLocker(dependency.hardlinkedLocation, async function () {
const newlyFetched = await fetchedPkg.fetchingFiles
const pkgJsonPath = path.join(dependency.hardlinkedLocation, 'package.json')
@@ -293,38 +288,6 @@ async function isSameFile (file1: string, file2: string) {
return stats[0].ino === stats[1].ino
}
function addToGraph (graph: Graph, dependent: string, dependency: InstalledPackage) {
dependent = dependent || GRAPH_ENTRY
graph[dependent] = graph[dependent] || {}
graph[dependent].dependencies = graph[dependent].dependencies || {}
updateDependencyResolution(graph, dependent, dependency.pkg.name, dependency.id)
graph[dependency.id] = graph[dependency.id] || {}
graph[dependency.id].dependents = graph[dependency.id].dependents || []
if (graph[dependency.id].dependents.indexOf(dependent) === -1) {
graph[dependency.id].dependents.push(dependent)
}
}
function updateDependencyResolution (graph: Graph, dependent: string, depName: string, newDepId: string) {
if (graph[dependent].dependencies[depName] &&
graph[dependent].dependencies[depName] !== newDepId) {
removeIfNoDependents(graph, graph[dependent].dependencies[depName], dependent)
}
graph[dependent].dependencies[depName] = newDepId
}
function removeIfNoDependents(graph: Graph, id: string, removedDependent: string) {
if (graph[id] && graph[id].dependents && graph[id].dependents.length === 1 &&
graph[id].dependents[0] === removedDependent) {
Object.keys(graph[id].dependencies || {}).forEach(depName => removeIfNoDependents(graph, graph[id].dependencies[depName], id))
delete graph[id]
}
}
async function isInstallable (
pkg: Package,
fetchedPkg: FetchedPackage,

View File

@@ -2,12 +2,11 @@ import path = require('path')
import normalizePath = require('normalize-path')
import fs = require('mz/fs')
import mkdirp = require('mkdirp-promise')
import readPkg from '../fs/readPkg'
import safeReadPkg from '../fs/safeReadPkg'
import getPkgDirs from '../fs/getPkgDirs'
import binify from '../binify'
import isWindows = require('is-windows')
import cmdShim = require('@zkochan/cmd-shim')
import {Package} from '../types'
import logger from 'pnpm-logger'
import Module = require('module')
import R = require('ramda')
@@ -54,12 +53,3 @@ async function getBinNodePaths (target: string) {
Module._nodeModulePaths(target)
)
}
async function safeReadPkg (pkgPath: string): Promise<Package | null> {
try {
return await readPkg(pkgPath)
} catch (err) {
if ((<NodeJS.ErrnoException>err).code !== 'ENOENT') throw err
return null
}
}

View File

@@ -9,7 +9,6 @@ test('API', t => {
t.equal(typeof pnpm.link, 'function', 'exports link()')
t.equal(typeof pnpm.linkToGlobal, 'function', 'exports linkToGlobal()')
t.equal(typeof pnpm.prune, 'function', 'exports prune()')
t.equal(typeof pnpm.prunePkgs, 'function', 'exports prunePkgs()')
t.end()
})

View File

@@ -2,7 +2,7 @@ import tape = require('tape')
import promisifyTape from 'tape-promise'
const test = promisifyTape(tape)
import path = require('path')
import {installPkgs, prune, prunePkgs} from '../src'
import {installPkgs, prune} from '../src'
import {prepare, testDefaults} from './utils'
import exists = require('path-exists')
import existsSymlink = require('exists-link')
@@ -42,40 +42,6 @@ test('prune removes extraneous packages', async function (t) {
await project.has('fnumber')
})
test('prune removes only the specified extraneous packages', async function (t) {
const project = prepare(t)
await installPkgs(['is-positive@2.0.0', 'is-negative@2.1.0'], testDefaults())
const pkg = await readPkg()
delete pkg.dependencies['is-positive']
delete pkg.dependencies['is-negative']
await writePkg(pkg)
await prunePkgs(['is-positive'], testDefaults())
await project.storeHasNot('is-positive', '2.0.0')
await project.hasNot('is-positive')
await project.storeHas('is-negative', '2.1.0')
await project.has('is-negative')
})
test('prune throws error when trying to removes not an extraneous package', async function (t) {
prepare(t)
await installPkgs(['is-positive@2.0.0'], testDefaults({save: true}))
try {
await prunePkgs(['is-positive'], testDefaults())
t.fail('prune had to fail')
} catch (err) {
t.equal(err['code'], 'PRUNE_NOT_EXTR', 'cannot prune non-extraneous package error thrown')
}
})
test('prune removes dev dependencies in production', async function (t) {
const project = prepare(t)

View File

@@ -69,7 +69,9 @@ test('uninstall package with dependencies and do not touch other deps', async fu
t.deepEqual(pkgJson.dependencies, {'is-negative': '^2.1.0'}, 'camelcase-keys has been removed from dependencies')
const shr = await project.loadShrinkwrap()
t.ok(!shr, 'camelcase-keys removed from shrinkwrap dependencies')
t.deepEqual(shr.dependencies, {
'is-negative@^2.1.0': 'localhost+4873/is-negative/2.1.0',
}, 'camelcase-keys removed from shrinkwrap dependencies')
})
test('uninstall package with its bin files', async function (t) {

View File

@@ -30,6 +30,7 @@
"src/api/link.ts",
"src/api/lock.ts",
"src/api/prune.ts",
"src/api/removeOrphanPkgs.ts",
"src/api/uninstall.ts",
"src/api/verify.ts",
"src/bin/pnpm.ts",
@@ -44,15 +45,16 @@
"src/cmd/uninstall.ts",
"src/cmd/update.ts",
"src/cmd/verify.ts",
"src/depsToSpecs.ts",
"src/err.ts",
"src/errorTypes.ts",
"src/fs/dirsum.ts",
"src/fs/expandTilde.ts",
"src/fs/getPkgDirs.ts",
"src/fs/gracefulify.ts",
"src/fs/graphController.ts",
"src/fs/modulesController.ts",
"src/fs/readPkg.ts",
"src/fs/safeReadPkg.ts",
"src/fs/shrinkwrap.ts",
"src/fs/storeController.ts",
"src/getSaveType.ts",
@@ -76,6 +78,8 @@
"src/resolve/index.ts",
"src/resolve/local.ts",
"src/resolve/npm/createNpmPkgId.ts",
"src/resolve/npm/getHost.ts",
"src/resolve/npm/getRegistryFolderName.ts",
"src/resolve/npm/index.ts",
"src/resolve/npm/loadPackageMeta.ts",
"src/resolve/tarball.ts",