11cb0ef41Sopenharmony_ciconst Fetcher = require('./fetcher.js') 21cb0ef41Sopenharmony_ciconst FileFetcher = require('./file.js') 31cb0ef41Sopenharmony_ciconst RemoteFetcher = require('./remote.js') 41cb0ef41Sopenharmony_ciconst DirFetcher = require('./dir.js') 51cb0ef41Sopenharmony_ciconst hashre = /^[a-f0-9]{40}$/ 61cb0ef41Sopenharmony_ciconst git = require('@npmcli/git') 71cb0ef41Sopenharmony_ciconst pickManifest = require('npm-pick-manifest') 81cb0ef41Sopenharmony_ciconst npa = require('npm-package-arg') 91cb0ef41Sopenharmony_ciconst { Minipass } = require('minipass') 101cb0ef41Sopenharmony_ciconst cacache = require('cacache') 111cb0ef41Sopenharmony_ciconst log = require('proc-log') 121cb0ef41Sopenharmony_ciconst npm = require('./util/npm.js') 131cb0ef41Sopenharmony_ci 141cb0ef41Sopenharmony_ciconst _resolvedFromRepo = Symbol('_resolvedFromRepo') 151cb0ef41Sopenharmony_ciconst _resolvedFromHosted = Symbol('_resolvedFromHosted') 161cb0ef41Sopenharmony_ciconst _resolvedFromClone = Symbol('_resolvedFromClone') 171cb0ef41Sopenharmony_ciconst _tarballFromResolved = Symbol.for('pacote.Fetcher._tarballFromResolved') 181cb0ef41Sopenharmony_ciconst _addGitSha = Symbol('_addGitSha') 191cb0ef41Sopenharmony_ciconst addGitSha = require('./util/add-git-sha.js') 201cb0ef41Sopenharmony_ciconst _clone = Symbol('_clone') 211cb0ef41Sopenharmony_ciconst _cloneHosted = Symbol('_cloneHosted') 221cb0ef41Sopenharmony_ciconst _cloneRepo = Symbol('_cloneRepo') 231cb0ef41Sopenharmony_ciconst _setResolvedWithSha = Symbol('_setResolvedWithSha') 241cb0ef41Sopenharmony_ciconst _prepareDir = Symbol('_prepareDir') 251cb0ef41Sopenharmony_ciconst _readPackageJson = Symbol.for('package.Fetcher._readPackageJson') 261cb0ef41Sopenharmony_ci 271cb0ef41Sopenharmony_ci// get the repository url. 281cb0ef41Sopenharmony_ci// prefer https if there's auth, since ssh will drop that. 291cb0ef41Sopenharmony_ci// otherwise, prefer ssh if available (more secure). 301cb0ef41Sopenharmony_ci// We have to add the git+ back because npa suppresses it. 311cb0ef41Sopenharmony_ciconst repoUrl = (h, opts) => 321cb0ef41Sopenharmony_ci h.sshurl && !(h.https && h.auth) && addGitPlus(h.sshurl(opts)) || 331cb0ef41Sopenharmony_ci h.https && addGitPlus(h.https(opts)) 341cb0ef41Sopenharmony_ci 351cb0ef41Sopenharmony_ci// add git+ to the url, but only one time. 361cb0ef41Sopenharmony_ciconst addGitPlus = url => url && `git+${url}`.replace(/^(git\+)+/, 'git+') 371cb0ef41Sopenharmony_ci 381cb0ef41Sopenharmony_ciclass GitFetcher extends Fetcher { 391cb0ef41Sopenharmony_ci constructor (spec, opts) { 401cb0ef41Sopenharmony_ci super(spec, opts) 411cb0ef41Sopenharmony_ci 421cb0ef41Sopenharmony_ci // we never want to compare integrity for git dependencies: npm/rfcs#525 431cb0ef41Sopenharmony_ci if (this.opts.integrity) { 441cb0ef41Sopenharmony_ci delete this.opts.integrity 451cb0ef41Sopenharmony_ci log.warn(`skipping integrity check for git dependency ${this.spec.fetchSpec}`) 461cb0ef41Sopenharmony_ci } 471cb0ef41Sopenharmony_ci 481cb0ef41Sopenharmony_ci this.resolvedRef = null 491cb0ef41Sopenharmony_ci if (this.spec.hosted) { 501cb0ef41Sopenharmony_ci this.from = this.spec.hosted.shortcut({ noCommittish: false }) 511cb0ef41Sopenharmony_ci } 521cb0ef41Sopenharmony_ci 531cb0ef41Sopenharmony_ci // shortcut: avoid full clone when we can go straight to the tgz 541cb0ef41Sopenharmony_ci // if we have the full sha and it's a hosted git platform 551cb0ef41Sopenharmony_ci if (this.spec.gitCommittish && hashre.test(this.spec.gitCommittish)) { 561cb0ef41Sopenharmony_ci this.resolvedSha = this.spec.gitCommittish 571cb0ef41Sopenharmony_ci // use hosted.tarball() when we shell to RemoteFetcher later 581cb0ef41Sopenharmony_ci this.resolved = this.spec.hosted 591cb0ef41Sopenharmony_ci ? repoUrl(this.spec.hosted, { noCommittish: false }) 601cb0ef41Sopenharmony_ci : this.spec.rawSpec 611cb0ef41Sopenharmony_ci } else { 621cb0ef41Sopenharmony_ci this.resolvedSha = '' 631cb0ef41Sopenharmony_ci } 641cb0ef41Sopenharmony_ci 651cb0ef41Sopenharmony_ci this.Arborist = opts.Arborist || null 661cb0ef41Sopenharmony_ci } 671cb0ef41Sopenharmony_ci 681cb0ef41Sopenharmony_ci // just exposed to make it easier to test all the combinations 691cb0ef41Sopenharmony_ci static repoUrl (hosted, opts) { 701cb0ef41Sopenharmony_ci return repoUrl(hosted, opts) 711cb0ef41Sopenharmony_ci } 721cb0ef41Sopenharmony_ci 731cb0ef41Sopenharmony_ci get types () { 741cb0ef41Sopenharmony_ci return ['git'] 751cb0ef41Sopenharmony_ci } 761cb0ef41Sopenharmony_ci 771cb0ef41Sopenharmony_ci resolve () { 781cb0ef41Sopenharmony_ci // likely a hosted git repo with a sha, so get the tarball url 791cb0ef41Sopenharmony_ci // but in general, no reason to resolve() more than necessary! 801cb0ef41Sopenharmony_ci if (this.resolved) { 811cb0ef41Sopenharmony_ci return super.resolve() 821cb0ef41Sopenharmony_ci } 831cb0ef41Sopenharmony_ci 841cb0ef41Sopenharmony_ci // fetch the git repo and then look at the current hash 851cb0ef41Sopenharmony_ci const h = this.spec.hosted 861cb0ef41Sopenharmony_ci // try to use ssh, fall back to git. 871cb0ef41Sopenharmony_ci return h ? this[_resolvedFromHosted](h) 881cb0ef41Sopenharmony_ci : this[_resolvedFromRepo](this.spec.fetchSpec) 891cb0ef41Sopenharmony_ci } 901cb0ef41Sopenharmony_ci 911cb0ef41Sopenharmony_ci // first try https, since that's faster and passphrase-less for 921cb0ef41Sopenharmony_ci // public repos, and supports private repos when auth is provided. 931cb0ef41Sopenharmony_ci // Fall back to SSH to support private repos 941cb0ef41Sopenharmony_ci // NB: we always store the https url in resolved field if auth 951cb0ef41Sopenharmony_ci // is present, otherwise ssh if the hosted type provides it 961cb0ef41Sopenharmony_ci [_resolvedFromHosted] (hosted) { 971cb0ef41Sopenharmony_ci return this[_resolvedFromRepo](hosted.https && hosted.https()) 981cb0ef41Sopenharmony_ci .catch(er => { 991cb0ef41Sopenharmony_ci // Throw early since we know pathspec errors will fail again if retried 1001cb0ef41Sopenharmony_ci if (er instanceof git.errors.GitPathspecError) { 1011cb0ef41Sopenharmony_ci throw er 1021cb0ef41Sopenharmony_ci } 1031cb0ef41Sopenharmony_ci const ssh = hosted.sshurl && hosted.sshurl() 1041cb0ef41Sopenharmony_ci // no fallthrough if we can't fall through or have https auth 1051cb0ef41Sopenharmony_ci if (!ssh || hosted.auth) { 1061cb0ef41Sopenharmony_ci throw er 1071cb0ef41Sopenharmony_ci } 1081cb0ef41Sopenharmony_ci return this[_resolvedFromRepo](ssh) 1091cb0ef41Sopenharmony_ci }) 1101cb0ef41Sopenharmony_ci } 1111cb0ef41Sopenharmony_ci 1121cb0ef41Sopenharmony_ci [_resolvedFromRepo] (gitRemote) { 1131cb0ef41Sopenharmony_ci // XXX make this a custom error class 1141cb0ef41Sopenharmony_ci if (!gitRemote) { 1151cb0ef41Sopenharmony_ci return Promise.reject(new Error(`No git url for ${this.spec}`)) 1161cb0ef41Sopenharmony_ci } 1171cb0ef41Sopenharmony_ci const gitRange = this.spec.gitRange 1181cb0ef41Sopenharmony_ci const name = this.spec.name 1191cb0ef41Sopenharmony_ci return git.revs(gitRemote, this.opts).then(remoteRefs => { 1201cb0ef41Sopenharmony_ci return gitRange ? pickManifest({ 1211cb0ef41Sopenharmony_ci versions: remoteRefs.versions, 1221cb0ef41Sopenharmony_ci 'dist-tags': remoteRefs['dist-tags'], 1231cb0ef41Sopenharmony_ci name, 1241cb0ef41Sopenharmony_ci }, gitRange, this.opts) 1251cb0ef41Sopenharmony_ci : this.spec.gitCommittish ? 1261cb0ef41Sopenharmony_ci remoteRefs.refs[this.spec.gitCommittish] || 1271cb0ef41Sopenharmony_ci remoteRefs.refs[remoteRefs.shas[this.spec.gitCommittish]] 1281cb0ef41Sopenharmony_ci : remoteRefs.refs.HEAD // no git committish, get default head 1291cb0ef41Sopenharmony_ci }).then(revDoc => { 1301cb0ef41Sopenharmony_ci // the committish provided isn't in the rev list 1311cb0ef41Sopenharmony_ci // things like HEAD~3 or @yesterday can land here. 1321cb0ef41Sopenharmony_ci if (!revDoc || !revDoc.sha) { 1331cb0ef41Sopenharmony_ci return this[_resolvedFromClone]() 1341cb0ef41Sopenharmony_ci } 1351cb0ef41Sopenharmony_ci 1361cb0ef41Sopenharmony_ci this.resolvedRef = revDoc 1371cb0ef41Sopenharmony_ci this.resolvedSha = revDoc.sha 1381cb0ef41Sopenharmony_ci this[_addGitSha](revDoc.sha) 1391cb0ef41Sopenharmony_ci return this.resolved 1401cb0ef41Sopenharmony_ci }) 1411cb0ef41Sopenharmony_ci } 1421cb0ef41Sopenharmony_ci 1431cb0ef41Sopenharmony_ci [_setResolvedWithSha] (withSha) { 1441cb0ef41Sopenharmony_ci // we haven't cloned, so a tgz download is still faster 1451cb0ef41Sopenharmony_ci // of course, if it's not a known host, we can't do that. 1461cb0ef41Sopenharmony_ci this.resolved = !this.spec.hosted ? withSha 1471cb0ef41Sopenharmony_ci : repoUrl(npa(withSha).hosted, { noCommittish: false }) 1481cb0ef41Sopenharmony_ci } 1491cb0ef41Sopenharmony_ci 1501cb0ef41Sopenharmony_ci // when we get the git sha, we affix it to our spec to build up 1511cb0ef41Sopenharmony_ci // either a git url with a hash, or a tarball download URL 1521cb0ef41Sopenharmony_ci [_addGitSha] (sha) { 1531cb0ef41Sopenharmony_ci this[_setResolvedWithSha](addGitSha(this.spec, sha)) 1541cb0ef41Sopenharmony_ci } 1551cb0ef41Sopenharmony_ci 1561cb0ef41Sopenharmony_ci [_resolvedFromClone] () { 1571cb0ef41Sopenharmony_ci // do a full or shallow clone, then look at the HEAD 1581cb0ef41Sopenharmony_ci // kind of wasteful, but no other option, really 1591cb0ef41Sopenharmony_ci return this[_clone](dir => this.resolved) 1601cb0ef41Sopenharmony_ci } 1611cb0ef41Sopenharmony_ci 1621cb0ef41Sopenharmony_ci [_prepareDir] (dir) { 1631cb0ef41Sopenharmony_ci return this[_readPackageJson](dir + '/package.json').then(mani => { 1641cb0ef41Sopenharmony_ci // no need if we aren't going to do any preparation. 1651cb0ef41Sopenharmony_ci const scripts = mani.scripts 1661cb0ef41Sopenharmony_ci if (!mani.workspaces && (!scripts || !( 1671cb0ef41Sopenharmony_ci scripts.postinstall || 1681cb0ef41Sopenharmony_ci scripts.build || 1691cb0ef41Sopenharmony_ci scripts.preinstall || 1701cb0ef41Sopenharmony_ci scripts.install || 1711cb0ef41Sopenharmony_ci scripts.prepack || 1721cb0ef41Sopenharmony_ci scripts.prepare))) { 1731cb0ef41Sopenharmony_ci return 1741cb0ef41Sopenharmony_ci } 1751cb0ef41Sopenharmony_ci 1761cb0ef41Sopenharmony_ci // to avoid cases where we have an cycle of git deps that depend 1771cb0ef41Sopenharmony_ci // on one another, we only ever do preparation for one instance 1781cb0ef41Sopenharmony_ci // of a given git dep along the chain of installations. 1791cb0ef41Sopenharmony_ci // Note that this does mean that a dependency MAY in theory end up 1801cb0ef41Sopenharmony_ci // trying to run its prepare script using a dependency that has not 1811cb0ef41Sopenharmony_ci // been properly prepared itself, but that edge case is smaller 1821cb0ef41Sopenharmony_ci // and less hazardous than a fork bomb of npm and git commands. 1831cb0ef41Sopenharmony_ci const noPrepare = !process.env._PACOTE_NO_PREPARE_ ? [] 1841cb0ef41Sopenharmony_ci : process.env._PACOTE_NO_PREPARE_.split('\n') 1851cb0ef41Sopenharmony_ci if (noPrepare.includes(this.resolved)) { 1861cb0ef41Sopenharmony_ci log.info('prepare', 'skip prepare, already seen', this.resolved) 1871cb0ef41Sopenharmony_ci return 1881cb0ef41Sopenharmony_ci } 1891cb0ef41Sopenharmony_ci noPrepare.push(this.resolved) 1901cb0ef41Sopenharmony_ci 1911cb0ef41Sopenharmony_ci // the DirFetcher will do its own preparation to run the prepare scripts 1921cb0ef41Sopenharmony_ci // All we have to do is put the deps in place so that it can succeed. 1931cb0ef41Sopenharmony_ci return npm( 1941cb0ef41Sopenharmony_ci this.npmBin, 1951cb0ef41Sopenharmony_ci [].concat(this.npmInstallCmd).concat(this.npmCliConfig), 1961cb0ef41Sopenharmony_ci dir, 1971cb0ef41Sopenharmony_ci { ...process.env, _PACOTE_NO_PREPARE_: noPrepare.join('\n') }, 1981cb0ef41Sopenharmony_ci { message: 'git dep preparation failed' } 1991cb0ef41Sopenharmony_ci ) 2001cb0ef41Sopenharmony_ci }) 2011cb0ef41Sopenharmony_ci } 2021cb0ef41Sopenharmony_ci 2031cb0ef41Sopenharmony_ci [_tarballFromResolved] () { 2041cb0ef41Sopenharmony_ci const stream = new Minipass() 2051cb0ef41Sopenharmony_ci stream.resolved = this.resolved 2061cb0ef41Sopenharmony_ci stream.from = this.from 2071cb0ef41Sopenharmony_ci 2081cb0ef41Sopenharmony_ci // check it out and then shell out to the DirFetcher tarball packer 2091cb0ef41Sopenharmony_ci this[_clone](dir => this[_prepareDir](dir) 2101cb0ef41Sopenharmony_ci .then(() => new Promise((res, rej) => { 2111cb0ef41Sopenharmony_ci if (!this.Arborist) { 2121cb0ef41Sopenharmony_ci throw new Error('GitFetcher requires an Arborist constructor to pack a tarball') 2131cb0ef41Sopenharmony_ci } 2141cb0ef41Sopenharmony_ci const df = new DirFetcher(`file:${dir}`, { 2151cb0ef41Sopenharmony_ci ...this.opts, 2161cb0ef41Sopenharmony_ci Arborist: this.Arborist, 2171cb0ef41Sopenharmony_ci resolved: null, 2181cb0ef41Sopenharmony_ci integrity: null, 2191cb0ef41Sopenharmony_ci }) 2201cb0ef41Sopenharmony_ci const dirStream = df[_tarballFromResolved]() 2211cb0ef41Sopenharmony_ci dirStream.on('error', rej) 2221cb0ef41Sopenharmony_ci dirStream.on('end', res) 2231cb0ef41Sopenharmony_ci dirStream.pipe(stream) 2241cb0ef41Sopenharmony_ci }))).catch( 2251cb0ef41Sopenharmony_ci /* istanbul ignore next: very unlikely and hard to test */ 2261cb0ef41Sopenharmony_ci er => stream.emit('error', er) 2271cb0ef41Sopenharmony_ci ) 2281cb0ef41Sopenharmony_ci return stream 2291cb0ef41Sopenharmony_ci } 2301cb0ef41Sopenharmony_ci 2311cb0ef41Sopenharmony_ci // clone a git repo into a temp folder (or fetch and unpack if possible) 2321cb0ef41Sopenharmony_ci // handler accepts a directory, and returns a promise that resolves 2331cb0ef41Sopenharmony_ci // when we're done with it, at which point, cacache deletes it 2341cb0ef41Sopenharmony_ci // 2351cb0ef41Sopenharmony_ci // TODO: after cloning, create a tarball of the folder, and add to the cache 2361cb0ef41Sopenharmony_ci // with cacache.put.stream(), using a key that's deterministic based on the 2371cb0ef41Sopenharmony_ci // spec and repo, so that we don't ever clone the same thing multiple times. 2381cb0ef41Sopenharmony_ci [_clone] (handler, tarballOk = true) { 2391cb0ef41Sopenharmony_ci const o = { tmpPrefix: 'git-clone' } 2401cb0ef41Sopenharmony_ci const ref = this.resolvedSha || this.spec.gitCommittish 2411cb0ef41Sopenharmony_ci const h = this.spec.hosted 2421cb0ef41Sopenharmony_ci const resolved = this.resolved 2431cb0ef41Sopenharmony_ci 2441cb0ef41Sopenharmony_ci // can be set manually to false to fall back to actual git clone 2451cb0ef41Sopenharmony_ci tarballOk = tarballOk && 2461cb0ef41Sopenharmony_ci h && resolved === repoUrl(h, { noCommittish: false }) && h.tarball 2471cb0ef41Sopenharmony_ci 2481cb0ef41Sopenharmony_ci return cacache.tmp.withTmp(this.cache, o, async tmp => { 2491cb0ef41Sopenharmony_ci // if we're resolved, and have a tarball url, shell out to RemoteFetcher 2501cb0ef41Sopenharmony_ci if (tarballOk) { 2511cb0ef41Sopenharmony_ci const nameat = this.spec.name ? `${this.spec.name}@` : '' 2521cb0ef41Sopenharmony_ci return new RemoteFetcher(h.tarball({ noCommittish: false }), { 2531cb0ef41Sopenharmony_ci ...this.opts, 2541cb0ef41Sopenharmony_ci allowGitIgnore: true, 2551cb0ef41Sopenharmony_ci pkgid: `git:${nameat}${this.resolved}`, 2561cb0ef41Sopenharmony_ci resolved: this.resolved, 2571cb0ef41Sopenharmony_ci integrity: null, // it'll always be different, if we have one 2581cb0ef41Sopenharmony_ci }).extract(tmp).then(() => handler(tmp), er => { 2591cb0ef41Sopenharmony_ci // fall back to ssh download if tarball fails 2601cb0ef41Sopenharmony_ci if (er.constructor.name.match(/^Http/)) { 2611cb0ef41Sopenharmony_ci return this[_clone](handler, false) 2621cb0ef41Sopenharmony_ci } else { 2631cb0ef41Sopenharmony_ci throw er 2641cb0ef41Sopenharmony_ci } 2651cb0ef41Sopenharmony_ci }) 2661cb0ef41Sopenharmony_ci } 2671cb0ef41Sopenharmony_ci 2681cb0ef41Sopenharmony_ci const sha = await ( 2691cb0ef41Sopenharmony_ci h ? this[_cloneHosted](ref, tmp) 2701cb0ef41Sopenharmony_ci : this[_cloneRepo](this.spec.fetchSpec, ref, tmp) 2711cb0ef41Sopenharmony_ci ) 2721cb0ef41Sopenharmony_ci this.resolvedSha = sha 2731cb0ef41Sopenharmony_ci if (!this.resolved) { 2741cb0ef41Sopenharmony_ci await this[_addGitSha](sha) 2751cb0ef41Sopenharmony_ci } 2761cb0ef41Sopenharmony_ci return handler(tmp) 2771cb0ef41Sopenharmony_ci }) 2781cb0ef41Sopenharmony_ci } 2791cb0ef41Sopenharmony_ci 2801cb0ef41Sopenharmony_ci // first try https, since that's faster and passphrase-less for 2811cb0ef41Sopenharmony_ci // public repos, and supports private repos when auth is provided. 2821cb0ef41Sopenharmony_ci // Fall back to SSH to support private repos 2831cb0ef41Sopenharmony_ci // NB: we always store the https url in resolved field if auth 2841cb0ef41Sopenharmony_ci // is present, otherwise ssh if the hosted type provides it 2851cb0ef41Sopenharmony_ci [_cloneHosted] (ref, tmp) { 2861cb0ef41Sopenharmony_ci const hosted = this.spec.hosted 2871cb0ef41Sopenharmony_ci return this[_cloneRepo](hosted.https({ noCommittish: true }), ref, tmp) 2881cb0ef41Sopenharmony_ci .catch(er => { 2891cb0ef41Sopenharmony_ci // Throw early since we know pathspec errors will fail again if retried 2901cb0ef41Sopenharmony_ci if (er instanceof git.errors.GitPathspecError) { 2911cb0ef41Sopenharmony_ci throw er 2921cb0ef41Sopenharmony_ci } 2931cb0ef41Sopenharmony_ci const ssh = hosted.sshurl && hosted.sshurl({ noCommittish: true }) 2941cb0ef41Sopenharmony_ci // no fallthrough if we can't fall through or have https auth 2951cb0ef41Sopenharmony_ci if (!ssh || hosted.auth) { 2961cb0ef41Sopenharmony_ci throw er 2971cb0ef41Sopenharmony_ci } 2981cb0ef41Sopenharmony_ci return this[_cloneRepo](ssh, ref, tmp) 2991cb0ef41Sopenharmony_ci }) 3001cb0ef41Sopenharmony_ci } 3011cb0ef41Sopenharmony_ci 3021cb0ef41Sopenharmony_ci [_cloneRepo] (repo, ref, tmp) { 3031cb0ef41Sopenharmony_ci const { opts, spec } = this 3041cb0ef41Sopenharmony_ci return git.clone(repo, ref, tmp, { ...opts, spec }) 3051cb0ef41Sopenharmony_ci } 3061cb0ef41Sopenharmony_ci 3071cb0ef41Sopenharmony_ci manifest () { 3081cb0ef41Sopenharmony_ci if (this.package) { 3091cb0ef41Sopenharmony_ci return Promise.resolve(this.package) 3101cb0ef41Sopenharmony_ci } 3111cb0ef41Sopenharmony_ci 3121cb0ef41Sopenharmony_ci return this.spec.hosted && this.resolved 3131cb0ef41Sopenharmony_ci ? FileFetcher.prototype.manifest.apply(this) 3141cb0ef41Sopenharmony_ci : this[_clone](dir => 3151cb0ef41Sopenharmony_ci this[_readPackageJson](dir + '/package.json') 3161cb0ef41Sopenharmony_ci .then(mani => this.package = { 3171cb0ef41Sopenharmony_ci ...mani, 3181cb0ef41Sopenharmony_ci _resolved: this.resolved, 3191cb0ef41Sopenharmony_ci _from: this.from, 3201cb0ef41Sopenharmony_ci })) 3211cb0ef41Sopenharmony_ci } 3221cb0ef41Sopenharmony_ci 3231cb0ef41Sopenharmony_ci packument () { 3241cb0ef41Sopenharmony_ci return FileFetcher.prototype.packument.apply(this) 3251cb0ef41Sopenharmony_ci } 3261cb0ef41Sopenharmony_ci} 3271cb0ef41Sopenharmony_cimodule.exports = GitFetcher 328