diff --git a/__tests__/integration-git.js b/__tests__/integration-git.js new file mode 100644 index 0000000000..fb4856d75c --- /dev/null +++ b/__tests__/integration-git.js @@ -0,0 +1,164 @@ +/* @flow */ + +import path from 'path'; + +import GitServer from 'node-git-server'; +import getPort from 'get-port'; +import rimraf from 'rimraf'; +import execa from 'execa'; +import mkdirp from 'mkdirp'; +import makeTemp from './_temp.js'; +import * as fs from '../src/util/fs.js'; +import {promisify} from '../src/util/promise.js'; + +const createDir = promisify(mkdirp); +const remove = promisify(rimraf); + +describe('while adding git packages', () => { + const gitServerDirName = 'server'; + const gitClientDirName = 'client'; + const gitRepoOwner = 'john-doe'; + const gitRepoName = 'foo'; + let tempDir; + let gitServer; + let gitServerUrl; + let gitClientDir; + + beforeAll(async () => { + tempDir = await makeTemp(); + const gitServerDir = path.join(tempDir, gitServerDirName); + gitClientDir = path.join(tempDir, gitClientDirName); + await createDir(gitClientDir); + await createDir(gitServerDir); + const port = await getPort(); + const gitServerResult = createGitServer(gitServerDir); + await gitServerResult.listen(port); + gitServer = gitServerResult.gitServer; + gitServerUrl = `http://localhost:${port}`; + await prepareRepo(gitServerUrl, gitRepoOwner, gitRepoName, gitClientDir); + }); + + afterAll(async () => { + if (gitServer) { + gitServer.close(); + } + if (tempDir) { + await remove(tempDir); + } + }); + + test('yarn add should fetch tags for cached git repo', async () => { + const consumer = await createConsumer(); + const firstVersion = '1.0.0'; + const secondVersion = '2.0.0'; + + await createRepoFiles(gitRepoName, firstVersion, gitClientDir); + await consumer.add(`${gitServerUrl}/${gitRepoOwner}/${gitRepoName}.git#v${firstVersion}`); + + await createRepoFiles(gitRepoName, secondVersion, gitClientDir); + await consumer.add(`${gitServerUrl}/${gitRepoOwner}/${gitRepoName}.git#v${secondVersion}`); + + await consumer.clean(); + }); +}); + +function createGitServer(dir: string): {gitServer: GitServer, listen: Promise} { + const gitServer = new GitServer(dir); + + gitServer.on('push', push => { + push.accept(); + }); + + gitServer.on('tag', tag => { + tag.accept(); + }); + + gitServer.on('fetch', fetch => { + fetch.accept(); + }); + + gitServer.on('info', info => { + info.accept(); + }); + + gitServer.on('head', info => { + info.accept(); + }); + + const listen = (port: number) => + new Promise((resolve, reject) => { + gitServer.listen(port, err => { + if (err) { + return reject(err); + } + return resolve(); + }); + }); + + return {gitServer, listen}; +} + +async function prepareRepo( + gitRemote: string, + gitRepoOwner: string, + gitRepoName: string, + gitClientDir: string, +): Promise { + await execa('git', ['clone', `${gitRemote}/${gitRepoOwner}/${gitRepoName}`], { + cwd: gitClientDir, + }); +} + +async function createRepoFiles(name: string, version: string, gitClientDir: string): Promise { + const repoDir = path.join(gitClientDir, name); + + const indexJSContent = `module.exports = '${version}';\n`; + const indexJSPath = path.join(repoDir, 'index.js'); + await fs.writeFile(indexJSPath, indexJSContent); + + const pkgContent = JSON.stringify({name, version, license: 'MIT'}); + const pkgPath = path.join(repoDir, 'package.json'); + await fs.writeFile(pkgPath, pkgContent); + + await execa('git', ['add', '.'], { + cwd: repoDir, + }); + await execa('git', ['commit', '-m', '"Init"'], { + cwd: repoDir, + }); + const tagName = `v${version}`; + await execa('git', ['tag', tagName], { + cwd: repoDir, + }); + await execa('git', ['push', 'origin', 'master'], { + cwd: repoDir, + }); + await execa('git', ['push', 'origin', tagName], { + cwd: repoDir, + }); +} + +async function createConsumer(): Promise { + const cwd = await makeTemp(); + const cacheFolder = path.join(cwd, 'cache'); + const command = path.resolve(__dirname, '../bin/yarn'); + const options = {cwd}; + + await fs.writeFile( + path.join(cwd, 'package.json'), + JSON.stringify({ + name: 'test', + license: 'MIT', + }), + ); + + return { + add: async (pattern, yarnArgs: Array = []) => { + const args = ['--cache-folder', cacheFolder, ...yarnArgs]; + await execa(command, ['add', pattern].concat(args), options); + }, + clean: async () => { + await remove(cwd); + }, + }; +} diff --git a/package.json b/package.json index 136c4da4f7..9c0f9fc7b2 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "execa": "^0.10.0", "fancy-log": "^1.3.2", "flow-bin": "^0.66.0", + "get-port": "^4.0.0", "git-release-notes": "^3.0.0", "gulp": "^4.0.0", "gulp-babel": "^7.0.0", @@ -92,6 +93,7 @@ "jsinspect": "^0.12.6", "minimatch": "^3.0.4", "mock-stdin": "^0.3.0", + "node-git-server": "^0.4.3", "prettier": "^1.5.2", "temp": "^0.8.3", "webpack": "^2.1.0-beta.25", diff --git a/yarn.lock b/yarn.lock index 43e772b204..8afe77f837 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3212,6 +3212,11 @@ get-caller-file@^1.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== +get-port@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-4.0.0.tgz#373c85960138ee20027c070e3cb08019fea29816" + integrity sha512-Yy3yNI2oShgbaWg4cmPhWjkZfktEvpKI09aDX4PZzNtlU9obuYrX7x2mumQsrNxlF+Ls7OtMQW/u+X4s896bOQ== + get-stdin@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" @@ -5284,6 +5289,13 @@ node-fetch@1.6.3: encoding "^0.1.11" is-stream "^1.0.1" +node-git-server@^0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/node-git-server/-/node-git-server-0.4.3.tgz#02aa7231e7766a609392a77ca31d7c4ede9fd04e" + integrity sha512-Q+Z8ef4Tw4ZP738iDr9ofPNq4M0fDdQ6/73Ak8SZRCtSsiXXHBgaja8BD/+krUYH7Df0QicsuLyT/mTmElRu7A== + dependencies: + through "^2.3.8" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -7087,7 +7099,7 @@ through2@2.X, through2@^2.0.0, through2@^2.0.1, through2@^2.0.3, through2@~2.0.0 readable-stream "^2.1.5" xtend "~4.0.1" -through@^2.3.6: +through@^2.3.6, through@^2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=