From f0ae7a0ff2832faeea5e1fd5a8fe7ad5b3208c8d Mon Sep 17 00:00:00 2001 From: zkochan Date: Sun, 5 Mar 2017 15:20:49 +0200 Subject: [PATCH] fix: the `tag` config is used only for top-level named installs --- package.json | 2 + src/api/install.ts | 77 ++++++++++++++++++---------------- src/depsToSpecs.ts | 9 ++++ src/install/fetch.ts | 2 - src/install/installMultiple.ts | 40 ++++++------------ src/resolve/index.ts | 1 - src/resolve/npm/index.ts | 10 ++--- src/save.ts | 5 +-- test/install.ts | 12 ++++++ 9 files changed, 84 insertions(+), 74 deletions(-) create mode 100644 src/depsToSpecs.ts diff --git a/package.json b/package.json index baf1692fbc..a9ca8b9fc6 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/api/install.ts b/src/api/install.ts index 31fa7f3a7e..e606e004c8 100644 --- a/src/api/install.ts +++ b/src/api/install.ts @@ -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 -} diff --git a/src/depsToSpecs.ts b/src/depsToSpecs.ts new file mode 100644 index 0000000000..d87589a2b2 --- /dev/null +++ b/src/depsToSpecs.ts @@ -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 pkgs.map(npa) +} diff --git a/src/install/fetch.ts b/src/install/fetch.ts index c055a31215..9aea7e52e2 100644 --- a/src/install/fetch.ts +++ b/src/install/fetch.ts @@ -37,7 +37,6 @@ export default async function fetch ( storePath: string, localRegistry: string, metaCache: Map, - 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, }) diff --git a/src/install/installMultiple.ts b/src/install/installMultiple.ts index 33b5d658ce..e4cad421f7 100644 --- a/src/install/installMultiple.ts +++ b/src/install/installMultiple.ts @@ -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, - tag: string, got: Got, keypath?: string[], resolvedDependencies?: ResolvedDependencies, @@ -59,16 +59,13 @@ export default async function installAll ( ): Promise { 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, - tag: string, got: Got, keypath: string[], resolvedDependencies?: ResolvedDependencies, @@ -103,14 +99,8 @@ async function installMultiple ( baseNodeModules: string, } ): Promise { - 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, - tag: string, got: Got, keypath: string[], pkgId?: string, @@ -365,7 +354,6 @@ async function installDependencies ( storePath: string, localRegistry: string, metaCache: Map, - 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}` -} diff --git a/src/resolve/index.ts b/src/resolve/index.ts index b6eeeb7e7b..50664c2412 100644 --- a/src/resolve/index.ts +++ b/src/resolve/index.ts @@ -68,7 +68,6 @@ export type ResolveOptions = { localRegistry: string, metaCache: Map, root: string, - tag: string } /** diff --git a/src/resolve/npm/index.ts b/src/resolve/npm/index.ts index 0d77d0f987..5a912249a1 100644 --- a/src/resolve/npm/index.ts +++ b/src/resolve/npm/index.ts @@ -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] } diff --git a/src/save.ts b/src/save.ts index 954ee9fb21..d4d5955062 100644 --- a/src/save.ts +++ b/src/save.ts @@ -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 { @@ -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) diff --git a/test/install.ts b/test/install.ts index 92af465a1f..6d850f2109 100644 --- a/test/install.ts +++ b/test/install.ts @@ -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())