mirror of
https://github.com/pnpm/pnpm.git
synced 2026-03-23 17:41:50 -04:00
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:
10
docs/api.md
10
docs/api.md
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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: [],
|
||||
|
||||
@@ -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})
|
||||
}
|
||||
|
||||
64
src/api/removeOrphanPkgs.ts
Normal file
64
src/api/removeOrphanPkgs.ts
Normal 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)))
|
||||
)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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] === ':'
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
11
src/fs/safeReadPkg.ts
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user