source: trip-planner-front/node_modules/pacote/lib/git.js@ ceaed42

Last change on this file since ceaed42 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 11.0 KB
Line 
1const Fetcher = require('./fetcher.js')
2const FileFetcher = require('./file.js')
3const RemoteFetcher = require('./remote.js')
4const DirFetcher = require('./dir.js')
5const hashre = /^[a-f0-9]{40}$/
6const git = require('@npmcli/git')
7const pickManifest = require('npm-pick-manifest')
8const npa = require('npm-package-arg')
9const url = require('url')
10const Minipass = require('minipass')
11const cacache = require('cacache')
12const { promisify } = require('util')
13const readPackageJson = require('read-package-json-fast')
14const npm = require('./util/npm.js')
15
16const _resolvedFromRepo = Symbol('_resolvedFromRepo')
17const _resolvedFromHosted = Symbol('_resolvedFromHosted')
18const _resolvedFromClone = Symbol('_resolvedFromClone')
19const _tarballFromResolved = Symbol.for('pacote.Fetcher._tarballFromResolved')
20const _addGitSha = Symbol('_addGitSha')
21const addGitSha = require('./util/add-git-sha.js')
22const _clone = Symbol('_clone')
23const _cloneHosted = Symbol('_cloneHosted')
24const _cloneRepo = Symbol('_cloneRepo')
25const _setResolvedWithSha = Symbol('_setResolvedWithSha')
26const _prepareDir = Symbol('_prepareDir')
27
28// get the repository url.
29// prefer https if there's auth, since ssh will drop that.
30// otherwise, prefer ssh if available (more secure).
31// We have to add the git+ back because npa suppresses it.
32const repoUrl = (h, opts) =>
33 h.sshurl && !(h.https && h.auth) && addGitPlus(h.sshurl(opts)) ||
34 h.https && addGitPlus(h.https(opts))
35
36// add git+ to the url, but only one time.
37const addGitPlus = url => url && `git+${url}`.replace(/^(git\+)+/, 'git+')
38
39class GitFetcher extends Fetcher {
40 constructor (spec, opts) {
41 super(spec, opts)
42 this.resolvedRef = null
43 if (this.spec.hosted)
44 this.from = this.spec.hosted.shortcut({ noCommittish: false })
45
46 // shortcut: avoid full clone when we can go straight to the tgz
47 // if we have the full sha and it's a hosted git platform
48 if (this.spec.gitCommittish && hashre.test(this.spec.gitCommittish)) {
49 this.resolvedSha = this.spec.gitCommittish
50 // use hosted.tarball() when we shell to RemoteFetcher later
51 this.resolved = this.spec.hosted
52 ? repoUrl(this.spec.hosted, { noCommittish: false })
53 : this.spec.fetchSpec + '#' + this.spec.gitCommittish
54 } else
55 this.resolvedSha = ''
56 }
57
58 // just exposed to make it easier to test all the combinations
59 static repoUrl (hosted, opts) {
60 return repoUrl(hosted, opts)
61 }
62
63 get types () {
64 return ['git']
65 }
66
67 resolve () {
68 // likely a hosted git repo with a sha, so get the tarball url
69 // but in general, no reason to resolve() more than necessary!
70 if (this.resolved)
71 return super.resolve()
72
73 // fetch the git repo and then look at the current hash
74 const h = this.spec.hosted
75 // try to use ssh, fall back to git.
76 return h ? this[_resolvedFromHosted](h)
77 : this[_resolvedFromRepo](this.spec.fetchSpec)
78 }
79
80 // first try https, since that's faster and passphrase-less for
81 // public repos, and supports private repos when auth is provided.
82 // Fall back to SSH to support private repos
83 // NB: we always store the https url in resolved field if auth
84 // is present, otherwise ssh if the hosted type provides it
85 [_resolvedFromHosted] (hosted) {
86 return this[_resolvedFromRepo](hosted.https && hosted.https())
87 .catch(er => {
88 // Throw early since we know pathspec errors will fail again if retried
89 if (er instanceof git.errors.GitPathspecError)
90 throw er
91 const ssh = hosted.sshurl && hosted.sshurl()
92 // no fallthrough if we can't fall through or have https auth
93 if (!ssh || hosted.auth)
94 throw er
95 return this[_resolvedFromRepo](ssh)
96 })
97 }
98
99 [_resolvedFromRepo] (gitRemote) {
100 // XXX make this a custom error class
101 if (!gitRemote)
102 return Promise.reject(new Error(`No git url for ${this.spec}`))
103 const gitRange = this.spec.gitRange
104 const name = this.spec.name
105 return git.revs(gitRemote, this.opts).then(remoteRefs => {
106 return gitRange ? pickManifest({
107 versions: remoteRefs.versions,
108 'dist-tags': remoteRefs['dist-tags'],
109 name,
110 }, gitRange, this.opts)
111 : this.spec.gitCommittish ?
112 remoteRefs.refs[this.spec.gitCommittish] ||
113 remoteRefs.refs[remoteRefs.shas[this.spec.gitCommittish]]
114 : remoteRefs.refs.HEAD // no git committish, get default head
115 }).then(revDoc => {
116 // the committish provided isn't in the rev list
117 // things like HEAD~3 or @yesterday can land here.
118 if (!revDoc || !revDoc.sha)
119 return this[_resolvedFromClone]()
120
121 this.resolvedRef = revDoc
122 this.resolvedSha = revDoc.sha
123 this[_addGitSha](revDoc.sha)
124 return this.resolved
125 })
126 }
127
128 [_setResolvedWithSha] (withSha) {
129 // we haven't cloned, so a tgz download is still faster
130 // of course, if it's not a known host, we can't do that.
131 this.resolved = !this.spec.hosted ? withSha
132 : repoUrl(npa(withSha).hosted, { noCommittish: false })
133 }
134
135 // when we get the git sha, we affix it to our spec to build up
136 // either a git url with a hash, or a tarball download URL
137 [_addGitSha] (sha) {
138 this[_setResolvedWithSha](addGitSha(this.spec, sha))
139 }
140
141 [_resolvedFromClone] () {
142 // do a full or shallow clone, then look at the HEAD
143 // kind of wasteful, but no other option, really
144 return this[_clone](dir => this.resolved)
145 }
146
147 [_prepareDir] (dir) {
148 return readPackageJson(dir + '/package.json').then(mani => {
149 // no need if we aren't going to do any preparation.
150 const scripts = mani.scripts
151 if (!scripts || !(
152 scripts.postinstall ||
153 scripts.build ||
154 scripts.preinstall ||
155 scripts.install ||
156 scripts.prepare))
157 return
158
159 // to avoid cases where we have an cycle of git deps that depend
160 // on one another, we only ever do preparation for one instance
161 // of a given git dep along the chain of installations.
162 // Note that this does mean that a dependency MAY in theory end up
163 // trying to run its prepare script using a dependency that has not
164 // been properly prepared itself, but that edge case is smaller
165 // and less hazardous than a fork bomb of npm and git commands.
166 const noPrepare = !process.env._PACOTE_NO_PREPARE_ ? []
167 : process.env._PACOTE_NO_PREPARE_.split('\n')
168 if (noPrepare.includes(this.resolved)) {
169 this.log.info('prepare', 'skip prepare, already seen', this.resolved)
170 return
171 }
172 noPrepare.push(this.resolved)
173
174 // the DirFetcher will do its own preparation to run the prepare scripts
175 // All we have to do is put the deps in place so that it can succeed.
176 return npm(
177 this.npmBin,
178 [].concat(this.npmInstallCmd).concat(this.npmCliConfig),
179 dir,
180 { ...process.env, _PACOTE_NO_PREPARE_: noPrepare.join('\n') },
181 { message: 'git dep preparation failed' }
182 )
183 })
184 }
185
186 [_tarballFromResolved] () {
187 const stream = new Minipass()
188 stream.resolved = this.resolved
189 stream.integrity = this.integrity
190 stream.from = this.from
191
192 // check it out and then shell out to the DirFetcher tarball packer
193 this[_clone](dir => this[_prepareDir](dir)
194 .then(() => new Promise((res, rej) => {
195 const df = new DirFetcher(`file:${dir}`, {
196 ...this.opts,
197 resolved: null,
198 integrity: null,
199 })
200 const dirStream = df[_tarballFromResolved]()
201 dirStream.on('error', rej)
202 dirStream.on('end', res)
203 dirStream.pipe(stream)
204 }))).catch(
205 /* istanbul ignore next: very unlikely and hard to test */
206 er => stream.emit('error', er)
207 )
208 return stream
209 }
210
211 // clone a git repo into a temp folder (or fetch and unpack if possible)
212 // handler accepts a directory, and returns a promise that resolves
213 // when we're done with it, at which point, cacache deletes it
214 //
215 // TODO: after cloning, create a tarball of the folder, and add to the cache
216 // with cacache.put.stream(), using a key that's deterministic based on the
217 // spec and repo, so that we don't ever clone the same thing multiple times.
218 [_clone] (handler, tarballOk = true) {
219 const o = { tmpPrefix: 'git-clone' }
220 const ref = this.resolvedSha || this.spec.gitCommittish
221 const h = this.spec.hosted
222 const resolved = this.resolved
223
224 // can be set manually to false to fall back to actual git clone
225 tarballOk = tarballOk &&
226 h && resolved === repoUrl(h, { noCommittish: false }) && h.tarball
227
228 return cacache.tmp.withTmp(this.cache, o, tmp => {
229 // if we're resolved, and have a tarball url, shell out to RemoteFetcher
230 if (tarballOk) {
231 const nameat = this.spec.name ? `${this.spec.name}@` : ''
232 return new RemoteFetcher(h.tarball({ noCommittish: false }), {
233 ...this.opts,
234 allowGitIgnore: true,
235 pkgid: `git:${nameat}${this.resolved}`,
236 resolved: this.resolved,
237 integrity: null, // it'll always be different, if we have one
238 }).extract(tmp).then(() => handler(tmp), er => {
239 // fall back to ssh download if tarball fails
240 if (er.constructor.name.match(/^Http/))
241 return this[_clone](handler, false)
242 else
243 throw er
244 })
245 }
246
247 return (
248 h ? this[_cloneHosted](ref, tmp)
249 : this[_cloneRepo](this.spec.fetchSpec, ref, tmp)
250 ).then(sha => {
251 this.resolvedSha = sha
252 if (!this.resolved)
253 this[_addGitSha](sha)
254 })
255 .then(() => handler(tmp))
256 })
257 }
258
259 // first try https, since that's faster and passphrase-less for
260 // public repos, and supports private repos when auth is provided.
261 // Fall back to SSH to support private repos
262 // NB: we always store the https url in resolved field if auth
263 // is present, otherwise ssh if the hosted type provides it
264 [_cloneHosted] (ref, tmp) {
265 const hosted = this.spec.hosted
266 return this[_cloneRepo](hosted.https({ noCommittish: true }), ref, tmp)
267 .catch(er => {
268 // Throw early since we know pathspec errors will fail again if retried
269 if (er instanceof git.errors.GitPathspecError)
270 throw er
271 const ssh = hosted.sshurl && hosted.sshurl({ noCommittish: true })
272 // no fallthrough if we can't fall through or have https auth
273 if (!ssh || hosted.auth)
274 throw er
275 return this[_cloneRepo](ssh, ref, tmp)
276 })
277 }
278
279 [_cloneRepo] (repo, ref, tmp) {
280 const { opts, spec } = this
281 return git.clone(repo, ref, tmp, { ...opts, spec })
282 }
283
284 manifest () {
285 if (this.package)
286 return Promise.resolve(this.package)
287
288 return this.spec.hosted && this.resolved
289 ? FileFetcher.prototype.manifest.apply(this)
290 : this[_clone](dir =>
291 readPackageJson(dir + '/package.json')
292 .then(mani => this.package = {
293 ...mani,
294 _integrity: this.integrity && String(this.integrity),
295 _resolved: this.resolved,
296 _from: this.from,
297 }))
298 }
299
300 packument () {
301 return FileFetcher.prototype.packument.apply(this)
302 }
303}
304module.exports = GitFetcher
Note: See TracBrowser for help on using the repository browser.