<commit_before>import createDebug from '../debug'
const debug = createDebug('pnpm:install')
import npa = require('npm-package-arg')
import fs = require('mz/fs')
import {Stats} from 'fs'
import logger = require('@zkochan/logger')
import path = require('path')
import rimraf = require('rimraf-then')
import resolve, {ResolveResult} from '../resolve'
import mkdirp from '../fs/mkdirp'
import requireJson from '../fs/requireJson'
import linkDir from 'link-dir'
import exists = require('exists-file')
import isAvailable from './isAvailable'
import memoize, {CachedPromises} from '../memoize'
import {Package} from '../types'
import {Got} from '../network/got'
import {InstallContext} from '../api/install'
import fetchRes from './fetchResolution'

export type FetchOptions = {
  keypath?: string[],
  linkLocal: boolean,
  force: boolean,
  root: string,
  storePath: string,
  tag: string,
  got: Got,
  update?: boolean,
}

export type FetchedPackage = {
  fetchingPkg: Promise<Package>,
  fetchingFiles: Promise<void>,
  path: string,
  srcPath?: string,
  id: string,
  fromCache: boolean,
  abort(): Promise<void>,
}

export type InstallLog = (msg: string, data?: Object) => void

/**
 * Installs a package.
 *
 * What it does:
 *
 * - resolve() - resolve from registry.npmjs.org
 * - fetch() - download tarball into node_modules/.store/{name}@{version}
 * - recurse into its dependencies
 * - symlink node_modules/{name}
 *
 * @param {Object} ctx - the context.
 * @param {Object} pkgMeta - meta info about the package to install.
 *
 * @example
 *     install(ctx, 'rimraf@2', './node_modules')
 */
export default async function fetch (ctx: InstallContext, pkgRawSpec: string, modules: string, options: FetchOptions): Promise<FetchedPackage> {
  debug('installing ' + pkgRawSpec)

  // Preliminary spec data
  // => { raw, name, scope, type, spec, rawSpec }
  const spec = npa(pkgRawSpec)

  // Dependency path to the current package. Not actually needed anmyore
  // outside getting its length
  // => ['babel-core@6.4.5', 'babylon@6.4.5', 'babel-runtime@5.8.35']
  const keypath = (options && options.keypath || [])

  const log: InstallLog = logger.fork(spec).log.bind(null, 'progress', pkgRawSpec)

  try {
    let resolution = ctx.shrinkwrap[pkgRawSpec]
    if (!resolution) {
      // it might be a bundleDependency, in which case, don't bother
      const available = !options.force && await isAvailable(spec, modules)
      if (available) {
        const fetchedPkg = await saveCachedResolution()
        fetchedPkg.fetchingPkg.then(pkg => log('package.json', pkg))
        fetchedPkg.fetchingFiles.then(() => log('done'))
        return fetchedPkg
      }
    }
    if (!resolution || options.update) {
      resolution = await resolve(spec, {
        log,
        got: options.got,
        root: options.root,
        linkLocal: options.linkLocal,
        tag: options.tag
      })
      if (resolution.tarball || resolution.repo) {
        ctx.shrinkwrap[pkgRawSpec] = Object.assign({}, resolution)
        delete ctx.shrinkwrap[pkgRawSpec].pkg
        delete ctx.shrinkwrap[pkgRawSpec].fetch
        delete ctx.shrinkwrap[pkgRawSpec].root
      }
    }
    log('resolved', resolution)

    const target = path.join(options.storePath, resolution.id)

    const fetchingFiles = fetchToStoreCached({
      fetchLocks: ctx.fetchLocks,
      target,
      resolution,
      log,
      got: options.got,
      force: options.force,
    })

    const fetchingPkg = resolution.pkg
      ? Promise.resolve(resolution.pkg)
      : fetchingFiles.then(() => requireJson(path.join(target, 'package.json')))

    const fetchedPkg = {
      fetchingPkg,
      fetchingFiles,
      id: resolution.id,
      fromCache: false,
      path: target,
      srcPath: resolution.root,
      abort: async function () {
        try {
          await fetchingFiles
        } finally {
          return rimraf(target)
        }
      },
    }
    fetchedPkg.fetchingPkg.then(pkg => log('package.json', pkg))
    fetchedPkg.fetchingFiles.then(() => log('done'))
    return fetchedPkg
  } catch (err) {
    log('error', err)
    throw err
  }

  async function saveCachedResolution (): Promise<FetchedPackage> {
    const target = path.join(modules, spec.name)
    const stat: Stats = await fs.lstat(target)
    if (stat.isSymbolicLink()) {
      const linkPath = await fs.readlink(target)
      return save(path.resolve(linkPath, target))
    }
    return save(target)

    async function save (fullpath: string): Promise<FetchedPackage> {
      const data = await requireJson(path.join(fullpath, 'package.json'))
      return {
        fetchingPkg: Promise.resolve(data),
        fetchingFiles: Promise.resolve(),
        id: path.basename(fullpath),
        fromCache: true,
        path: fullpath,
        abort: () => Promise.resolve(),
      }
    }
  }
}

type FetchToStoreOptions = {
  fetchLocks: CachedPromises<void>,
  target: string,
  resolution: ResolveResult,
  log: InstallLog,
  got: Got,
  force: boolean,
}

/**
 * Fetch to `.store/lodash@4.0.0`
 * If an ongoing build is already working, use it. Also, if that ongoing build
 * is part of the dependency chain (ie, it's a circular dependency), use its stub
 */
function fetchToStoreCached (opts: FetchToStoreOptions): Promise<void> {
  return memoize(opts.fetchLocks, opts.resolution.id, async function () {
    if (!opts.force && await exists(opts.target)) return

    await rimraf(opts.target)

    opts.log('download-queued')
    await fetchRes(opts.resolution, opts.target, {got: opts.got, log: opts.log})

    const pkg = await requireJson(path.join(opts.target, 'package.json'))

    opts.log('package.json', pkg)
  })
}
<commit_msg>fix(install): logging of cached packages should show resolved version<commit_after>import createDebug from '../debug'
const debug = createDebug('pnpm:install')
import npa = require('npm-package-arg')
import fs = require('mz/fs')
import {Stats} from 'fs'
import logger = require('@zkochan/logger')
import path = require('path')
import rimraf = require('rimraf-then')
import resolve, {ResolveResult} from '../resolve'
import mkdirp from '../fs/mkdirp'
import requireJson from '../fs/requireJson'
import linkDir from 'link-dir'
import exists = require('exists-file')
import isAvailable from './isAvailable'
import memoize, {CachedPromises} from '../memoize'
import {Package} from '../types'
import {Got} from '../network/got'
import {InstallContext} from '../api/install'
import fetchRes from './fetchResolution'

export type FetchOptions = {
  keypath?: string[],
  linkLocal: boolean,
  force: boolean,
  root: string,
  storePath: string,
  tag: string,
  got: Got,
  update?: boolean,
}

export type FetchedPackage = {
  fetchingPkg: Promise<Package>,
  fetchingFiles: Promise<void>,
  path: string,
  srcPath?: string,
  id: string,
  fromCache: boolean,
  abort(): Promise<void>,
}

export type InstallLog = (msg: string, data?: Object) => void

/**
 * Installs a package.
 *
 * What it does:
 *
 * - resolve() - resolve from registry.npmjs.org
 * - fetch() - download tarball into node_modules/.store/{name}@{version}
 * - recurse into its dependencies
 * - symlink node_modules/{name}
 *
 * @param {Object} ctx - the context.
 * @param {Object} pkgMeta - meta info about the package to install.
 *
 * @example
 *     install(ctx, 'rimraf@2', './node_modules')
 */
export default async function fetch (ctx: InstallContext, pkgRawSpec: string, modules: string, options: FetchOptions): Promise<FetchedPackage> {
  debug('installing ' + pkgRawSpec)

  // Preliminary spec data
  // => { raw, name, scope, type, spec, rawSpec }
  const spec = npa(pkgRawSpec)

  // Dependency path to the current package. Not actually needed anmyore
  // outside getting its length
  // => ['babel-core@6.4.5', 'babylon@6.4.5', 'babel-runtime@5.8.35']
  const keypath = (options && options.keypath || [])

  const log: InstallLog = logger.fork(spec).log.bind(null, 'progress', pkgRawSpec)

  try {
    let resolution = ctx.shrinkwrap[pkgRawSpec]
    if (!resolution) {
      // it might be a bundleDependency, in which case, don't bother
      const available = !options.force && await isAvailable(spec, modules)
      if (available) {
        const fetchedPkg = await saveCachedResolution()
        fetchedPkg.fetchingPkg.then(pkg => log('package.json', pkg))
        fetchedPkg.fetchingFiles.then(() => log('done'))
        return fetchedPkg
      }
    }
    if (!resolution || options.update) {
      resolution = await resolve(spec, {
        log,
        got: options.got,
        root: options.root,
        linkLocal: options.linkLocal,
        tag: options.tag
      })
      if (resolution.tarball || resolution.repo) {
        ctx.shrinkwrap[pkgRawSpec] = Object.assign({}, resolution)
        delete ctx.shrinkwrap[pkgRawSpec].pkg
        delete ctx.shrinkwrap[pkgRawSpec].fetch
        delete ctx.shrinkwrap[pkgRawSpec].root
      }
    }
    log('resolved', resolution)

    const target = path.join(options.storePath, resolution.id)

    const fetchingFiles = fetchToStoreCached({
      fetchLocks: ctx.fetchLocks,
      target,
      resolution,
      log,
      got: options.got,
      force: options.force,
    })

    const fetchingPkg = resolution.pkg
      ? Promise.resolve(resolution.pkg)
      : fetchingFiles.then(() => requireJson(path.join(target, 'package.json')))

    const fetchedPkg = {
      fetchingPkg,
      fetchingFiles,
      id: resolution.id,
      fromCache: false,
      path: target,
      srcPath: resolution.root,
      abort: async function () {
        try {
          await fetchingFiles
        } finally {
          return rimraf(target)
        }
      },
    }
    fetchedPkg.fetchingPkg.then(pkg => log('package.json', pkg))
    fetchedPkg.fetchingFiles.then(() => log('done'))
    return fetchedPkg
  } catch (err) {
    log('error', err)
    throw err
  }

  async function saveCachedResolution (): Promise<FetchedPackage> {
    const target = path.join(modules, spec.name)
    const stat: Stats = await fs.lstat(target)
    if (stat.isSymbolicLink()) {
      const linkPath = await fs.readlink(target)
      return save(path.resolve(linkPath, target))
    }
    return save(target)

    async function save (fullpath: string): Promise<FetchedPackage> {
      const data = await requireJson(path.join(fullpath, 'package.json'))
      return {
        fetchingPkg: Promise.resolve(data),
        fetchingFiles: Promise.resolve(),
        id: path.basename(fullpath),
        fromCache: true,
        path: fullpath,
        abort: () => Promise.resolve(),
      }
    }
  }
}

type FetchToStoreOptions = {
  fetchLocks: CachedPromises<void>,
  target: string,
  resolution: ResolveResult,
  log: InstallLog,
  got: Got,
  force: boolean,
}

/**
 * Fetch to `.store/lodash@4.0.0`
 * If an ongoing build is already working, use it. Also, if that ongoing build
 * is part of the dependency chain (ie, it's a circular dependency), use its stub
 */
function fetchToStoreCached (opts: FetchToStoreOptions): Promise<void> {
  return memoize(opts.fetchLocks, opts.resolution.id, async function () {
    if (opts.force || !await exists(opts.target)) {
      await rimraf(opts.target)

      opts.log('download-queued')
      await fetchRes(opts.resolution, opts.target, {got: opts.got, log: opts.log})
    }

    const pkg = await requireJson(path.join(opts.target, 'package.json'))
    opts.log('package.json', pkg)
  })
}

{'fraction_non_alphanumeric': 0.1307994757536042, 'fraction_numerical': 0.0024464831804281344, 'mean_word_length': 3.1896046852122986, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 37, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6774378', 'n_tokens_mistral': 3588, 'n_tokens_neox': 3506, 'n_words': 919}