fix: the tag config is used only for top-level named installs

This commit is contained in:
zkochan
2017-03-05 15:20:49 +02:00
parent 2977102fef
commit f0ae7a0ff2
9 changed files with 84 additions and 74 deletions

View File

@@ -20,6 +20,7 @@
"@types/load-json-file": "2.0.5",
"@types/mz": "0.0.30",
"@types/node": "7.0.5",
"@types/ramda": "0.0.3",
"@types/rc": "0.0.1",
"@types/semver": "5.3.30",
"@types/update-notifier": "1.0.0",
@@ -70,6 +71,7 @@
"pnpm-install-checks": "1.1.0",
"pnpm-logger": "0.2.0",
"proper-lockfile": "2.0.0",
"ramda": "0.23.0",
"rc": "1.1.7",
"read-pkg": "2.0.0",
"read-pkg-up": "2.0.0",

View File

@@ -32,6 +32,7 @@ import createMemoize, {MemoizedFunc} from '../memoize'
import linkBins from '../install/linkBins'
import {Package} from '../types'
import {PackageSpec} from '../resolve'
import depsToSpecs from '../depsToSpecs'
export type InstalledPackages = {
[name: string]: InstalledPackage
@@ -53,12 +54,18 @@ export async function install (maybeOpts?: PnpmOptions) {
const installCtx = await createInstallCmd(opts, ctx.graph, ctx.shrinkwrap)
if (!ctx.pkg) throw new Error('No package.json found')
const packagesToInstall = Object.assign({}, ctx.pkg.dependencies || {})
if (!opts.production) Object.assign(packagesToInstall, ctx.pkg.devDependencies || {})
const optionalDeps = ctx.pkg.optionalDependencies || {}
const depsToInstall = Object.assign(
{},
!opts.production && ctx.pkg.devDependencies,
optionalDeps,
ctx.pkg.dependencies
)
const specs = depsToSpecs(depsToInstall)
return lock(
ctx.storePath,
() => installInContext('general', packagesToInstall, ctx, installCtx, opts),
() => installInContext('general', specs, Object.keys(optionalDeps), ctx, installCtx, opts),
{stale: opts.lockStaleDuration}
)
}
@@ -70,46 +77,65 @@ export async function install (maybeOpts?: PnpmOptions) {
* install({'lodash': '1.0.0', 'foo': '^2.1.0' }, { silent: true })
*/
export async function installPkgs (fuzzyDeps: string[] | Dependencies, maybeOpts?: PnpmOptions) {
let packagesToInstall = mapify(fuzzyDeps)
const opts = extendOptions(maybeOpts)
let packagesToInstall = Array.isArray(fuzzyDeps)
? argsToSpecs(fuzzyDeps, opts.tag)
: depsToSpecs(fuzzyDeps)
if (!Object.keys(packagesToInstall).length) {
throw new Error('At least one package has to be installed')
}
const opts = extendOptions(maybeOpts)
const ctx = await getContext(opts)
const installCtx = await createInstallCmd(opts, ctx.graph, ctx.shrinkwrap)
return lock(
ctx.storePath,
() => installInContext('named', packagesToInstall, ctx, installCtx, opts),
() => installInContext('named', packagesToInstall, [], ctx, installCtx, opts),
{stale: opts.lockStaleDuration}
)
}
function argsToSpecs (args: string[], defaultTag: string): PackageSpec[] {
return args
.map(arg => npa(arg))
.map(spec => {
if (spec.type === 'tag' && !spec.raw.endsWith('@latest')) {
spec.spec = defaultTag
}
return spec
})
}
function getResolutions(
packagesToInstall: Dependencies,
packagesToInstall: PackageSpec[],
resolvedSpecDeps: ResolvedDependencies
): ResolvedDependencies {
return Object.keys(packagesToInstall)
.reduce((resolvedDeps, depName) => {
const spec = `${depName}@${packagesToInstall[depName]}`
if (resolvedSpecDeps[spec]) {
resolvedDeps[depName] = resolvedSpecDeps[spec]
return packagesToInstall
.reduce((resolvedDeps, depSpec) => {
if (resolvedSpecDeps[depSpec.raw]) {
resolvedDeps[depSpec.name] = resolvedSpecDeps[depSpec.raw]
}
return resolvedDeps
}, {})
}
async function installInContext (installType: string, packagesToInstall: Dependencies, ctx: PnpmContext, installCtx: InstallContext, opts: StrictPnpmOptions) {
async function installInContext (
installType: string,
packagesToInstall: PackageSpec[],
optionalDependencies: string[],
ctx: PnpmContext,
installCtx: InstallContext,
opts: StrictPnpmOptions
) {
// TODO: ctx.graph should not be muted. installMultiple should return a new graph
const oldGraph: Graph = cloneDeep(ctx.graph)
const nodeModulesPath = path.join(ctx.root, 'node_modules')
const client = new RegClient(adaptConfig(opts))
const optionalDependencies = ctx.pkg && ctx.pkg && ctx.pkg.optionalDependencies || {}
const resolvedDependencies: ResolvedDependencies | undefined = installType !== 'general'
? undefined
: getResolutions(
Object.assign({}, optionalDependencies, packagesToInstall),
packagesToInstall,
ctx.shrinkwrap.dependencies
)
const installOpts = {
@@ -118,7 +144,6 @@ async function installInContext (installType: string, packagesToInstall: Depende
localRegistry: opts.localRegistry,
force: opts.force,
depth: opts.depth,
tag: opts.tag,
engineStrict: opts.engineStrict,
nodeVersion: opts.nodeVersion,
got: createGot(client, {networkConcurrency: opts.networkConcurrency}),
@@ -143,10 +168,8 @@ async function installInContext (installType: string, packagesToInstall: Depende
if (!ctx.pkg) {
throw new Error('Cannot save because no package.json found')
}
const inputNames = Object.keys(packagesToInstall)
const savedPackages = pkgs.filter((pkg: InstalledPackage) => inputNames.indexOf(pkg.pkg.name) > -1)
const pkgJsonPath = path.join(ctx.root, 'package.json')
newPkg = await save(pkgJsonPath, savedPackages, saveType, opts.saveExact)
newPkg = await save(pkgJsonPath, pkgs.map(pkg => pkg.pkg), saveType, opts.saveExact)
}
}
@@ -315,19 +338,3 @@ function installLogger (pkgId: string) {
lifecycleLogger[logLevel]({pkgId, line})
}
}
function mapify (pkgs: string[] | Dependencies): Dependencies {
if (!pkgs) return {}
if (Array.isArray(pkgs)) {
return pkgs.reduce((pkgsMap: Dependencies, pkgRequest: string) => {
const matches = /(@?[^@]+)@(.*)/.exec(pkgRequest)
if (!matches) {
pkgsMap[pkgRequest] = '*'
} else {
pkgsMap[matches[1]] = matches[2]
}
return pkgsMap
}, {})
}
return pkgs
}

9
src/depsToSpecs.ts Normal file
View File

@@ -0,0 +1,9 @@
import npa = require('npm-package-arg')
import {Dependencies} from './types'
import {PackageSpec} from './resolve'
export default function (deps: Dependencies): PackageSpec[] {
if (!deps) return []
const pkgs = Object.keys(deps).map(pkgName => `${pkgName}@${deps[pkgName]}`)
return <PackageSpec[]>pkgs.map(npa)
}

View File

@@ -37,7 +37,6 @@ export default async function fetch (
storePath: string,
localRegistry: string,
metaCache: Map<string, PackageMeta>,
tag: string,
got: Got,
update?: boolean,
shrinkwrapResolution?: Resolution,
@@ -55,7 +54,6 @@ export default async function fetch (
loggedPkg: options.loggedPkg,
root: options.root,
got: options.got,
tag: options.tag,
localRegistry: options.localRegistry,
metaCache: options.metaCache,
})

View File

@@ -1,7 +1,7 @@
import path = require('path')
import npa = require('npm-package-arg')
import logger from 'pnpm-logger'
import pFilter = require('p-filter')
import R = require('ramda')
import fetch, {FetchedPackage} from './fetch'
import {InstallContext, InstalledPackages} from '../api/install'
import {Dependencies} from '../types'
@@ -24,6 +24,7 @@ import {
import {PackageSpec, PackageMeta} from '../resolve'
import linkBins from '../install/linkBins'
import getLinkTarget = require('get-link-target')
import depsToSpecs from '../depsToSpecs'
const installCheckLogger = logger('install-check')
@@ -38,8 +39,8 @@ export type InstalledPackage = {
export default async function installAll (
ctx: InstallContext,
dependencies: Dependencies,
optionalDependencies: Dependencies,
specs: PackageSpec[],
optionalDependencies: string[],
modules: string,
options: {
force: boolean,
@@ -47,7 +48,6 @@ export default async function installAll (
storePath: string,
localRegistry: string,
metaCache: Map<string, PackageMeta>,
tag: string,
got: Got,
keypath?: string[],
resolvedDependencies?: ResolvedDependencies,
@@ -59,16 +59,13 @@ export default async function installAll (
): Promise<InstalledPackage[]> {
const keypath = options.keypath || []
const nonOptionalDependencies = Object.keys(dependencies)
.filter(depName => !optionalDependencies[depName])
.reduce((nonOptionalDependencies, depName) => {
nonOptionalDependencies[depName] = dependencies[depName]
return nonOptionalDependencies
}, {})
const specGroups = R.partition((spec: PackageSpec) => !!spec.name && R.contains(spec.name, optionalDependencies), specs)
const optionalDepSpecs = specGroups[0]
const nonOptionalDepSpecs = specGroups[1]
const installedPkgs: InstalledPackage[] = Array.prototype.concat.apply([], await Promise.all([
installMultiple(ctx, nonOptionalDependencies, modules, Object.assign({}, options, {optional: false, keypath})),
installMultiple(ctx, optionalDependencies, modules, Object.assign({}, options, {optional: true, keypath})),
installMultiple(ctx, nonOptionalDepSpecs, modules, Object.assign({}, options, {optional: false, keypath})),
installMultiple(ctx, optionalDepSpecs, modules, Object.assign({}, options, {optional: true, keypath})),
]))
await Promise.all(
@@ -84,7 +81,7 @@ export default async function installAll (
async function installMultiple (
ctx: InstallContext,
pkgsMap: Dependencies,
specs: PackageSpec[],
modules: string,
options: {
force: boolean,
@@ -92,7 +89,6 @@ async function installMultiple (
storePath: string,
localRegistry: string,
metaCache: Map<string, PackageMeta>,
tag: string,
got: Got,
keypath: string[],
resolvedDependencies?: ResolvedDependencies,
@@ -103,14 +99,8 @@ async function installMultiple (
baseNodeModules: string,
}
): Promise<InstalledPackage[]> {
pkgsMap = pkgsMap || {}
const pkgs = Object.keys(pkgsMap).map(pkgName => getRawSpec(pkgName, pkgsMap[pkgName]))
ctx.graph = ctx.graph || {}
const specs = pkgs.map(npa)
const nonLinkedPkgs = modules === options.baseNodeModules
// only check modules on the first level
? await pFilter(specs, (spec: PackageSpec) => !spec.name || isInnerLink(modules, spec.name))
@@ -176,7 +166,6 @@ async function install (
storePath: string,
localRegistry: string,
metaCache: Map<string, PackageMeta>,
tag: string,
got: Got,
keypath: string[],
pkgId?: string,
@@ -365,7 +354,6 @@ async function installDependencies (
storePath: string,
localRegistry: string,
metaCache: Map<string, PackageMeta>,
tag: string,
got: Got,
keypath: string[],
resolvedDependencies?: ResolvedDependencies,
@@ -383,8 +371,8 @@ async function installDependencies (
const bundledDeps = pkg.bundleDependencies || pkg.bundledDependencies || []
const filterDeps = getNotBundledDeps.bind(null, bundledDeps)
const deps = filterDeps(pkg.dependencies || {})
const optionalDeps = filterDeps(pkg.optionalDependencies || {})
const deps = depsToSpecs(filterDeps(Object.assign({}, pkg.optionalDependencies, pkg.dependencies)))
const optionalDeps = Object.keys(pkg.optionalDependencies || {})
const installedDeps: InstalledPackage[] = await installAll(ctx, deps, optionalDeps, modules, depsInstallOpts)
@@ -407,7 +395,3 @@ function addInstalledPkg (installs: InstalledPackages, newPkg: InstalledPackage)
}
installs[newPkg.id].optional = installs[newPkg.id].optional && newPkg.optional
}
function getRawSpec (name: string, version: string) {
return version === '*' ? name : `${name}@${version}`
}

View File

@@ -68,7 +68,6 @@ export type ResolveOptions = {
localRegistry: string,
metaCache: Map<string, PackageMeta>,
root: string,
tag: string
}
/**

View File

@@ -28,7 +28,7 @@ export default async function resolveNpm (spec: PackageSpec, opts: ResolveOption
try {
if (opts.loggedPkg) logStatus({ status: 'resolving', pkg: opts.loggedPkg })
const meta = await loadPkgMeta(spec, opts.localRegistry, opts.got, opts.metaCache)
const correctPkg = pickVersion(meta, spec, opts.tag)
const correctPkg = pickVersion(meta, spec)
if (!correctPkg) {
const versions = Object.keys(meta.versions)
const message = versions.length
@@ -54,11 +54,11 @@ export default async function resolveNpm (spec: PackageSpec, opts: ResolveOption
}
}
function pickVersion (meta: PackageMeta, dep: PackageSpec, latestTag: string) {
function pickVersion (meta: PackageMeta, dep: PackageSpec) {
if (dep.type === 'tag') {
return pickVersionByTag(meta, dep.spec)
}
return pickVersionByVersionRange(meta, dep.spec, latestTag)
return pickVersionByVersionRange(meta, dep.spec)
}
function pickVersionByTag(meta: PackageMeta, tag: string) {
@@ -69,8 +69,8 @@ function pickVersionByTag(meta: PackageMeta, tag: string) {
return null
}
function pickVersionByVersionRange(meta: PackageMeta, versionRange: string, latestTag: string) {
const latest = meta['dist-tags'][latestTag]
function pickVersionByVersionRange(meta: PackageMeta, versionRange: string) {
const latest = meta['dist-tags']['latest']
if (semver.satisfies(latest, versionRange, true)) {
return meta.versions[latest]
}

View File

@@ -1,12 +1,11 @@
import {ignoreCache as readPkg} from './fs/readPkg'
import writePkg = require('write-pkg')
import {DependenciesType} from './getSaveType'
import {InstalledPackage} from './install/installMultiple'
import {Package} from './types'
export default async function save (
pkgJsonPath: string,
installedPackages: InstalledPackage[],
installedPackages: Package[],
saveType: DependenciesType,
useExactVersion: boolean
): Promise<Package> {
@@ -15,7 +14,7 @@ export default async function save (
packageJson[saveType] = packageJson[saveType] || {}
installedPackages.forEach(dependency => {
const semverCharacter = useExactVersion ? '' : '^'
packageJson[saveType][dependency.pkg.name] = semverCharacter + dependency.pkg.version
packageJson[saveType][dependency.name] = semverCharacter + dependency.version
})
await writePkg(pkgJsonPath, packageJson)

View File

@@ -57,6 +57,18 @@ test('scoped modules without version spec (@rstacruz/tap-spec)', async function
t.ok(typeof _ === 'function', 'tap-spec is available')
})
test('modules without version spec, with custom tag config', async function (t) {
const project = prepare(t)
const tag = 'beta'
await addDistTag('dep-of-pkg-with-1-dep', '100.0.0', tag)
await installPkgs(['dep-of-pkg-with-1-dep'], testDefaults({tag}))
await project.storeHas('dep-of-pkg-with-1-dep', '100.0.0')
})
test('scoped modules with versions (@rstacruz/tap-spec@4.1.1)', async function (t) {
const project = prepare(t)
await installPkgs(['@rstacruz/tap-spec@4.1.1'], testDefaults())