Skip to content
This repository has been archived by the owner on Sep 1, 2022. It is now read-only.

Commit

Permalink
Install archived yarn versions (#275)
Browse files Browse the repository at this point in the history
* wip: get release from tags

* wip: handle 404s in download file

* wip: extract into specified destination path

* feat: use archive package when git unavailable

* test: installation branches

* chore: add self contributor

* cr: naming and http status code

* cr: use arg to skip validation

* test: update install test

* fix: build yarn from src

* cr: use npm to run yarn build

* cr: do not support defective yarn release version

* test: remove unused imports

* fix: clean up download if failed

* refactor: use download path name unique from version matching

* cr: clear up install command

* docs: manually increment contrib count

* refactor: readd options to install latest
  • Loading branch information
iamogbz authored and jakebolam committed Jan 5, 2019
1 parent a82693b commit e70fe04
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 69 deletions.
10 changes: 10 additions & 0 deletions .all-contributorsrc
Expand Up @@ -163,6 +163,16 @@
"contributions": [
"infra"
]
},
{
"login": "iamogbz",
"name": "Emmanuel Ogbizi",
"avatar_url": "https://avatars0.githubusercontent.com/u/2528959?v=4",
"profile": "http://emmanuel.ogbizi.com/",
"contributions": [
"code",
"test"
]
}
]
}
4 changes: 2 additions & 2 deletions README.md
Expand Up @@ -26,7 +26,7 @@
</a>
<br />
<a href="#contributors">
<img alt="All Contributors" src="https://img.shields.io/badge/all_contributors-17-orange.svg?style=flat-square"/>
<img alt="All Contributors" src="https://img.shields.io/badge/all_contributors-18-orange.svg?style=flat-square"/>
</a>
<a href="https://opensource.tophat.com/slack">
<img alt="Slack workspace" src="https://slackinvite.dev.tophat.com/badge.svg"/>
Expand Down Expand Up @@ -206,7 +206,7 @@ Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds
| [<img src="https://avatars3.githubusercontent.com/u/3876970?v=4" width="100px;"/><br /><sub><b>Francois Campbell</b></sub>](https://github.com/francoiscampbell)<br />[💻](https://github.com/tophat/yvm/commits?author=francoiscampbell "Code") | [<img src="https://avatars2.githubusercontent.com/u/3534236?v=4" width="100px;"/><br /><sub><b>Jake Bolam</b></sub>](https://jakebolam.com)<br />[📖](https://github.com/tophat/yvm/commits?author=jakebolam "Documentation") [💻](https://github.com/tophat/yvm/commits?author=jakebolam "Code") [🚇](#infra-jakebolam "Infrastructure (Hosting, Build-Tools, etc)") | [<img src="https://avatars1.githubusercontent.com/u/39271619?v=4" width="100px;"/><br /><sub><b>Brandon Baksh</b></sub>](https://www.linkedin.com/in/brandonbaksh/)<br />[💻](https://github.com/tophat/yvm/commits?author=brandonbaksh "Code") | [<img src="https://avatars3.githubusercontent.com/u/2070398?v=4" width="100px;"/><br /><sub><b>Milan Milojic</b></sub>](https://github.com/nepodmitljivi)<br />[💻](https://github.com/tophat/yvm/commits?author=nepodmitljivi "Code") | [<img src="https://avatars2.githubusercontent.com/u/38886386?v=4" width="100px;"/><br /><sub><b>Umar Ahmed</b></sub>](https://github.com/umar-tophat)<br />[💻](https://github.com/tophat/yvm/commits?author=umar-tophat "Code") | [<img src="https://avatars0.githubusercontent.com/u/3258756?v=4" width="100px;"/><br /><sub><b>Nicholas Dujay</b></sub>](https://github.com/dat2)<br />[💻](https://github.com/tophat/yvm/commits?author=dat2 "Code") | [<img src="https://avatars0.githubusercontent.com/u/3996927?v=4" width="100px;"/><br /><sub><b>Aser Eldamaty</b></sub>](https://github.com/aeldamaty)<br />[💻](https://github.com/tophat/yvm/commits?author=aeldamaty "Code") |
| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| [<img src="https://avatars3.githubusercontent.com/u/3495264?v=4" width="100px;"/><br /><sub><b>Michael Rose</b></sub>](http://msrose.github.io)<br />[💻](https://github.com/tophat/yvm/commits?author=msrose "Code") | [<img src="https://avatars0.githubusercontent.com/u/8632167?v=4" width="100px;"/><br /><sub><b>Sanchit Gera</b></sub>](http://www.sanchitgera.ca)<br />[📖](https://github.com/tophat/yvm/commits?author=sanchitgera "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/6020693?v=4" width="100px;"/><br /><sub><b>sdcosta</b></sub>](https://github.com/sdcosta)<br />[📖](https://github.com/tophat/yvm/commits?author=sdcosta "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/445636?v=4" width="100px;"/><br /><sub><b>Siavash Mahmoudian</b></sub>](https://breezio.com)<br />[🚇](#infra-syavash "Infrastructure (Hosting, Build-Tools, etc)") | [<img src="https://avatars3.githubusercontent.com/in/505?v=4" width="100px;"/><br /><sub><b>greenkeeper[bot]</b></sub>](https://github.com/apps/greenkeeper)<br />[🚇](#infra-greenkeeper[bot] "Infrastructure (Hosting, Build-Tools, etc)") | [<img src="https://avatars0.githubusercontent.com/u/7827407?v=4" width="100px;"/><br /><sub><b>Jay Crumb</b></sub>](https://github.com/jcrumb)<br />[📖](https://github.com/tophat/yvm/commits?author=jcrumb "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/1097941?v=4" width="100px;"/><br /><sub><b>Michael Lunøe</b></sub>](http://m.lunoe.dk)<br />[📖](https://github.com/tophat/yvm/commits?author=mlunoe "Documentation") |
| [<img src="https://avatars3.githubusercontent.com/u/1558352?v=4" width="100px;"/><br /><sub><b>Yash Shah</b></sub>](http://www.yashshah.com)<br />[💻](https://github.com/tophat/yvm/commits?author=yashshah "Code") | [<img src="https://avatars0.githubusercontent.com/u/9504927?v=4" width="100px;"/><br /><sub><b>Wacław Schiller</b></sub>](https://github.com/torinthiel)<br />[💻](https://github.com/tophat/yvm/commits?author=torinthiel "Code") | [<img src="https://avatars0.githubusercontent.com/u/45925873?v=4" width="100px;"/><br /><sub><b>yvm-bot</b></sub>](https://github.com/yvm-bot)<br />[🚇](#infra-yvm-bot "Infrastructure (Hosting, Build-Tools, etc)") |
| [<img src="https://avatars3.githubusercontent.com/u/1558352?v=4" width="100px;"/><br /><sub><b>Yash Shah</b></sub>](http://www.yashshah.com)<br />[💻](https://github.com/tophat/yvm/commits?author=yashshah "Code") | [<img src="https://avatars0.githubusercontent.com/u/9504927?v=4" width="100px;"/><br /><sub><b>Wacław Schiller</b></sub>](https://github.com/torinthiel)<br />[💻](https://github.com/tophat/yvm/commits?author=torinthiel "Code") | [<img src="https://avatars0.githubusercontent.com/u/45925873?v=4" width="100px;"/><br /><sub><b>yvm-bot</b></sub>](https://github.com/yvm-bot)<br />[🚇](#infra-yvm-bot "Infrastructure (Hosting, Build-Tools, etc)") | [<img src="https://avatars0.githubusercontent.com/u/2528959?v=4" width="100px;"/><br /><sub><b>Emmanuel Ogbizi</b></sub>](http://emmanuel.ogbizi.com/)<br />[💻](https://github.com/tophat/yvm/commits?author=iamogbz "Code") [⚠️](https://github.com/tophat/yvm/commits?author=iamogbz "Tests") |
<!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
Expand Down
79 changes: 51 additions & 28 deletions src/commands/install.js
Expand Up @@ -8,12 +8,15 @@ const log = require('../util/log')
const {
versionRootPath,
getExtractionPath,
getReleasesFromTags,
getVersionsFromTags,
} = require('../util/utils')
const { yvmPath } = require('../util/path')

const { colors } = log

const getDownloadPath = (version, rootPath) =>
path.resolve(rootPath, 'versions', `v${version}.tar.gz`)
path.resolve(rootPath, 'versions', `yarn-v${version}.tar.gz`)

const getSignatureDownloadPath = (version, rootPath) =>
`${getDownloadPath(version, rootPath)}.asc`
Expand All @@ -30,10 +33,17 @@ const isVersionInstalled = (version, rootPath) => {
return fs.existsSync(versionPath)
}

const downloadVersion = (version, rootPath) => {
const downloadVersion = async (version, rootPath) => {
const url = getUrl(version)
const filePath = getDownloadPath(version, rootPath)
return downloadFile(url, filePath)
try {
await downloadFile(url, filePath)
return true
} catch (e) {
if (fs.existsSync(filePath)) fs.unlinkSync(filePath)
log(colors.RED, e)
return false
}
}

const downloadSignature = (version, rootPath) => {
Expand Down Expand Up @@ -78,34 +88,39 @@ const verifySignature = async (version, rootPath) => {
}

const extractYarn = (version, rootPath) => {
const destPath = versionRootPath(rootPath)
const destPath = getExtractionPath(version, rootPath)
const tmpPath = `${destPath}.tar.gz.tmp`
const srcPath = getDownloadPath(version, rootPath)

return new Promise((resolve, reject) => {
targz.decompress(
{
src: srcPath,
dest: destPath,
dest: tmpPath,
},
err => {
if (err) {
log(err)
reject(err)
} else {
log(`Finished extracting yarn version ${version}`)
fs.renameSync(
`${destPath}/yarn-v${version}`,
`${destPath}/v${version}`,
)
fs.unlinkSync(srcPath)
resolve()
const [pkgDir] = fs.readdirSync(tmpPath)
if (pkgDir) {
const pkgPath = path.resolve(tmpPath, pkgDir)
fs.renameSync(pkgPath, destPath)
fs.unlinkSync(srcPath)
fs.rmdirSync(tmpPath)
resolve(destPath)
} else {
reject('Unable to located extracted package')
}
}
},
)
})
}

const installVersion = async (version, rootPath = yvmPath) => {
const installVersion = async ({ version, rootPath = yvmPath }) => {
if (!fs.existsSync(versionRootPath(rootPath))) {
fs.mkdirSync(versionRootPath(rootPath))
}
Expand All @@ -115,33 +130,36 @@ const installVersion = async (version, rootPath = yvmPath) => {
return
}

const versions = await getVersionsFromTags()
if (versions.indexOf(version) === -1) {
const releases = await getReleasesFromTags()
if (!releases.hasOwnProperty(version)) {
log(
'You have provided an invalid version number. use "yvm ls-remote" to see valid versions.',
)
throw new Error('Invalid version number provided')
}

log(`Installing yarn v${version} in ${rootPath}`)
await downloadVersion(version, rootPath)

log('Downloading...')
if (!(await downloadVersion(version, rootPath))) {
log(`Installation aborted, probably caused by a defective release`)
log(`\u2717 https://github.com/yarnpkg/yarn/releases/tag/v${version}`)
log('Please retry with the next available version')
return
}
log(`Finished downloading yarn version ${version}`)
await verifySignature(version, rootPath)

log('Validating...')
await verifySignature(version, rootPath)
log('GPG signature validated')
return extractYarn(version, rootPath)

log('Extracting...')
await extractYarn(version, rootPath)
log('Installation successful')
}

const installLatest = (rootPath = yvmPath) => {
return getVersionsFromTags()
.then(versions => {
const latestVersion = versions[0]
return installVersion(latestVersion, rootPath)
})
.catch(err => {
log(err)
})
const installLatest = async (options = {}) => {
const [latestVersion] = await getVersionsFromTags()
return installVersion(Object.assign(options, { version: latestVersion }))
}

const ensureVersionInstalled = (version, rootPath = yvmPath) => {
Expand All @@ -152,4 +170,9 @@ const ensureVersionInstalled = (version, rootPath = yvmPath) => {
return installVersion(version, rootPath)
}

module.exports = { installVersion, installLatest, ensureVersionInstalled }
module.exports = {
installVersion,
installLatest,
getDownloadPath,
ensureVersionInstalled,
}
22 changes: 13 additions & 9 deletions src/util/download.js
@@ -1,17 +1,21 @@
const fs = require('fs')
const request = require('request')

const downloadFile = (url, filePath) => {
const file = fs.createWriteStream(filePath)
const isErrorCode = httpStatusCode => httpStatusCode >= 400

return new Promise((resolve, reject) => {
const stream = request.get(url).pipe(file)
stream.on('finish', () => resolve())
stream.on('error', err => {
reject(new Error(err))
})
const downloadFile = (url, filePath) =>
new Promise((resolve, reject) => {
const handleError = err => reject(err)
request
.get(url, { headers: { 'user-agent': 'yvm' } })
.on('error', handleError)
.on('response', r => {
const msg = `HTTP ${r.statusCode} - ${r.statusMessage} (${url})`
if (isErrorCode(r.statusCode)) handleError(msg)
})
.pipe(fs.createWriteStream(filePath))
.on('finish', () => resolve())
})
}

module.exports = {
downloadFile,
Expand Down
6 changes: 6 additions & 0 deletions src/util/log.js
Expand Up @@ -15,4 +15,10 @@ log.info = function errorLog(...args) {
}
}

log.colors = {
RED: '\x1b[31m%s\x1b[0m',
GREEN: '\x1b[32m%s\x1b[0m',
YELLOW: '\x1b[33m%s\x1b[0m',
}

module.exports = log
18 changes: 12 additions & 6 deletions src/util/utils.js
Expand Up @@ -11,7 +11,7 @@ const getExtractionPath = (version, rootPath) =>
const stripVersionPrefix = tagName =>
tagName[0] === 'v' ? tagName.substring(1) : tagName

const getVersionsFromTags = () => {
const getReleasesFromTags = () => {
const options = {
url: 'https://d236jo9e8rrdox.cloudfront.net/yarn-releases',
headers: {
Expand All @@ -32,18 +32,24 @@ const getVersionsFromTags = () => {
reject(error)
} else {
const tags = JSON.parse(body)
const tagNames = tags.map(tag => tag.name)
const versions = tagNames
.map(stripVersionPrefix)
.filter(version => version[0] > 0)
resolve(versions)
const releases = tags.reduce((accumulator, tag) => {
const version = stripVersionPrefix(tag.name)
const [major] = version.split('.')
return Number(major) > 0
? Object.assign(accumulator, { [version]: tag })
: accumulator
}, {})
resolve(releases)
}
})
})
}

const getVersionsFromTags = async () => Object.keys(await getReleasesFromTags())

module.exports = {
getExtractionPath,
getReleasesFromTags,
getVersionsFromTags,
stripVersionPrefix,
versionRootPath,
Expand Down
5 changes: 3 additions & 2 deletions src/util/version.js
Expand Up @@ -11,6 +11,7 @@ const DEFAULT_VERSION_TEXT = 'Global Default'
const VERSION_REGEX = /\d+(\.\d+){2}(.*)/
const VERSION_IN_USE_SYMBOL = '\u2713'
const VERSION_INSTALLED_SYMBOL = '\u2192'
const { colors } = log

function isValidVersionString(version) {
return VERSION_REGEX.test(version)
Expand Down Expand Up @@ -161,8 +162,8 @@ const printVersions = ({
if (isDefault) toLog += ` (${DEFAULT_VERSION_TEXT})`

const logArgs = []
if (isCurrent) logArgs.push('\x1b[32m%s\x1b[0m')
else if (isInstalled) logArgs.push('\x1b[33m%s\x1b[0m')
if (isCurrent) logArgs.push(colors.GREEN)
else if (isInstalled) logArgs.push(colors.YELLOW)
log(...logArgs, toLog)

versionsMap[version] = toLog
Expand Down
21 changes: 7 additions & 14 deletions src/yvm.js
Expand Up @@ -35,23 +35,16 @@ argParser
.description('Install the specified version of Yarn.')
.action((maybeVersion, command) => {
const { installVersion, installLatest } = require('./commands/install')
if (command.latest) {
installLatest()
return
const handleError = error => {
log(error)
process.exit(1)
}

if (maybeVersion) {
installVersion(maybeVersion).catch(error => {
log(error)
process.exit(1)
})
if (command.latest) {
installLatest().catch(handleError)
return
}
const [defaultVersion] = getSplitVersionAndArgs()
installVersion(defaultVersion).catch(error => {
log(error)
process.exit(1)
})
const version = maybeVersion || getSplitVersionAndArgs()[0]
installVersion({ version }).catch(handleError)
})

argParser
Expand Down

0 comments on commit e70fe04

Please sign in to comment.