• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict'
2
3const BB = require('bluebird')
4
5const cacache = require('cacache')
6const cacheKey = require('../util/cache-key')
7const Fetcher = require('../fetch')
8const git = require('../util/git')
9const mkdirp = BB.promisify(require('mkdirp'))
10const pickManifest = require('npm-pick-manifest')
11const optCheck = require('../util/opt-check')
12const osenv = require('osenv')
13const packDir = require('../util/pack-dir')
14const PassThrough = require('stream').PassThrough
15const path = require('path')
16const pipe = BB.promisify(require('mississippi').pipe)
17const rimraf = BB.promisify(require('rimraf'))
18const uniqueFilename = require('unique-filename')
19
20// `git` dependencies are fetched from git repositories and packed up.
21const fetchGit = module.exports = Object.create(null)
22
23Fetcher.impl(fetchGit, {
24  packument (spec, opts) {
25    return BB.reject(new Error('Not implemented yet.'))
26  },
27
28  manifest (spec, opts) {
29    opts = optCheck(opts)
30    if (spec.hosted && spec.hosted.getDefaultRepresentation() === 'shortcut') {
31      return hostedManifest(spec, opts)
32    } else {
33      // If it's not a shortcut, don't do fallbacks.
34      return plainManifest(spec.fetchSpec, spec, opts)
35    }
36  },
37
38  tarball (spec, opts) {
39    opts = optCheck(opts)
40    const stream = new PassThrough()
41    this.manifest(spec, opts).then(manifest => {
42      stream.emit('manifest', manifest)
43      return pipe(
44        this.fromManifest(
45          manifest, spec, opts
46        ).on('integrity', i => stream.emit('integrity', i)), stream
47      )
48    }).catch(err => stream.emit('error', err))
49    return stream
50  },
51
52  fromManifest (manifest, spec, opts) {
53    opts = optCheck(opts)
54    let streamError
55    const stream = new PassThrough().on('error', e => { streamError = e })
56    const cacheName = manifest._uniqueResolved || manifest._resolved || ''
57    const cacheStream = (
58      opts.cache &&
59      cacache.get.stream(
60        opts.cache, cacheKey('packed-dir', cacheName), opts
61      ).on('integrity', i => stream.emit('integrity', i))
62    )
63    cacheStream.pipe(stream)
64    cacheStream.on('error', err => {
65      if (err.code !== 'ENOENT') {
66        return stream.emit('error', err)
67      } else {
68        stream.emit('reset')
69        return withTmp(opts, tmp => {
70          if (streamError) { throw streamError }
71          return cloneRepo(
72            spec, manifest._repo, manifest._ref, manifest._rawRef, tmp, opts
73          ).then(HEAD => {
74            if (streamError) { throw streamError }
75            manifest._resolved = spec.saveSpec.replace(/(:?#.*)?$/, `#${HEAD}`)
76            manifest._uniqueResolved = manifest._resolved
77            return packDir(manifest, manifest._uniqueResolved, tmp, stream, opts)
78          })
79        }).catch(err => stream.emit('error', err))
80      }
81    })
82    return stream
83  }
84})
85
86function hostedManifest (spec, opts) {
87  return BB.resolve(null).then(() => {
88    if (!spec.hosted.git()) {
89      throw new Error(`No git url for ${spec}`)
90    }
91    return plainManifest(spec.hosted.git(), spec, opts)
92  }).catch(err => {
93    if (!spec.hosted.https()) {
94      throw err
95    }
96    return plainManifest(spec.hosted.https(), spec, opts)
97  }).catch(err => {
98    if (!spec.hosted.sshurl()) {
99      throw err
100    }
101    return plainManifest(spec.hosted.sshurl(), spec, opts)
102  })
103}
104
105function plainManifest (repo, spec, opts) {
106  const rawRef = spec.gitCommittish || spec.gitRange
107  return resolve(
108    repo, spec, spec.name, opts
109  ).then(ref => {
110    if (ref) {
111      const resolved = spec.saveSpec.replace(/(?:#.*)?$/, `#${ref.sha}`)
112      return {
113        _repo: repo,
114        _resolved: resolved,
115        _spec: spec,
116        _ref: ref,
117        _rawRef: spec.gitCommittish || spec.gitRange,
118        _uniqueResolved: resolved,
119        _integrity: false,
120        _shasum: false
121      }
122    } else {
123      // We're SOL and need a full clone :(
124      //
125      // If we're confident enough that `rawRef` is a commit SHA,
126      // then we can at least get `finalize-manifest` to cache its result.
127      const resolved = spec.saveSpec.replace(/(?:#.*)?$/, rawRef ? `#${rawRef}` : '')
128      return {
129        _repo: repo,
130        _rawRef: rawRef,
131        _resolved: rawRef && rawRef.match(/^[a-f0-9]{40}$/) && resolved,
132        _uniqueResolved: rawRef && rawRef.match(/^[a-f0-9]{40}$/) && resolved,
133        _integrity: false,
134        _shasum: false
135      }
136    }
137  })
138}
139
140function resolve (url, spec, name, opts) {
141  const isSemver = !!spec.gitRange
142  return git.revs(url, opts).then(remoteRefs => {
143    return isSemver
144      ? pickManifest({
145        versions: remoteRefs.versions,
146        'dist-tags': remoteRefs['dist-tags'],
147        name: name
148      }, spec.gitRange, opts)
149      : remoteRefs
150        ? BB.resolve(
151          remoteRefs.refs[spec.gitCommittish] || remoteRefs.refs[remoteRefs.shas[spec.gitCommittish]]
152        )
153        : null
154  })
155}
156
157function withTmp (opts, cb) {
158  if (opts.cache) {
159    // cacache has a special facility for working in a tmp dir
160    return cacache.tmp.withTmp(opts.cache, { tmpPrefix: 'git-clone' }, cb)
161  } else {
162    const tmpDir = path.join(osenv.tmpdir(), 'pacote-git-tmp')
163    const tmpName = uniqueFilename(tmpDir, 'git-clone')
164    const tmp = mkdirp(tmpName).then(() => tmpName).disposer(rimraf)
165    return BB.using(tmp, cb)
166  }
167}
168
169// Only certain whitelisted hosted gits support shadow cloning
170const SHALLOW_HOSTS = new Set(['github', 'gist', 'gitlab', 'bitbucket'])
171function cloneRepo (spec, repo, resolvedRef, rawRef, tmp, opts) {
172  const ref = resolvedRef ? resolvedRef.ref : rawRef
173  if (resolvedRef && spec.hosted && SHALLOW_HOSTS.has(spec.hosted.type)) {
174    return git.shallow(repo, ref, tmp, opts)
175  } else {
176    return git.clone(repo, ref, tmp, opts)
177  }
178}
179