diff --git a/CHANGELOG.md b/CHANGELOG.md index 88442452d4..15e7d9252a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ Please add one entry in this file for each change in Yarn's behavior. Use the sa ## Master +- Implements `yarn policies set-version [range]`. Check [the documentation]() for usage & tips. + + [#6673](https://github.com/yarnpkg/yarn/pull/6673) - [**Maël Nison**](https://twitter.com/arcanis) + - Fixes a resolution issue when a package had an invalid `main` entry [#6682](https://github.com/yarnpkg/yarn/pull/6682) - [**Maël Nison**](https://twitter.com/arcanis) diff --git a/src/cli/commands/index.js b/src/cli/commands/index.js index 182f9958aa..2a8f6a852f 100644 --- a/src/cli/commands/index.js +++ b/src/cli/commands/index.js @@ -32,6 +32,7 @@ import * as node from './node.js'; import * as outdated from './outdated.js'; import * as owner from './owner.js'; import * as pack from './pack.js'; +import * as policies from './policies.js'; import * as publish from './publish.js'; import * as remove from './remove.js'; import * as run from './run.js'; @@ -78,6 +79,7 @@ const commands = { outdated, owner, pack, + policies, prune: buildUseless("The prune command isn't necessary. `yarn install` will prune extraneous packages."), publish, remove, diff --git a/src/cli/commands/policies.js b/src/cli/commands/policies.js new file mode 100644 index 0000000000..595f42b5aa --- /dev/null +++ b/src/cli/commands/policies.js @@ -0,0 +1,160 @@ +/* @flow */ + +import type {Reporter} from '../../reporters/index.js'; +import type Config from '../../config.js'; +import buildSubCommands from './_build-sub-commands.js'; +import {getRcConfigForCwd} from '../../rc.js'; +import * as fs from '../../util/fs.js'; +import {stringify} from '../../lockfile'; + +const chalk = require('chalk'); +const invariant = require('invariant'); +const path = require('path'); +const semver = require('semver'); + +type ReleaseAsset = {| + id: any, + + name: string, + browser_download_url: string, +|}; + +type Release = {| + id: any, + + draft: boolean, + prerelease: boolean, + + tag_name: string, + version: {| + version: string, + |}, + + assets: Array, +|}; + +function getBundleAsset(release: Release): ?ReleaseAsset { + return release.assets.find(asset => { + return asset.name.match(/^yarn-[0-9]+\.[0-9]+\.[0-9]+\.js$/); + }); +} + +type FetchReleasesOptions = {| + includePrereleases: boolean, +|}; + +async function fetchReleases( + config: Config, + {includePrereleases = false}: FetchReleasesOptions = {}, +): Promise> { + const request: Array = await config.requestManager.request({ + url: `https://api.github.com/repos/yarnpkg/yarn/releases`, + json: true, + }); + + const releases = request.filter(release => { + if (release.draft) { + return false; + } + + if (release.prerelease && !includePrereleases) { + return false; + } + + // $FlowFixMe + release.version = semver.coerce(release.tag_name); + + if (!release.version) { + return false; + } + + if (!getBundleAsset(release)) { + return false; + } + + return true; + }); + + releases.sort((a, b) => { + // $FlowFixMe + return -semver.compare(a.version, b.version); + }); + + return releases; +} + +function fetchBundle(config: Config, url: string): Promise { + return config.requestManager.request({ + url, + buffer: true, + }); +} + +export function hasWrapper(flags: Object, args: Array): boolean { + return false; +} + +const {run, setFlags, examples} = buildSubCommands('policies', { + async setVersion(config: Config, reporter: Reporter, flags: Object, args: Array): Promise { + let range = args[0] || 'latest'; + let allowRc = flags.rc; + + reporter.log(`Resolving ${chalk.yellow(range)} to a url...`); + + if (range === 'rc') { + range = 'latest'; + allowRc = true; + } + + if (range === 'latest') { + range = '*'; + } + + let bundleUrl; + let bundleVersion; + + if (range === 'nightly' || range === 'nightlies') { + bundleUrl = 'https://nightly.yarnpkg.com/latest.js'; + bundleVersion = 'nightly'; + } else { + const releases = await fetchReleases(config, { + includePrereleases: allowRc, + }); + + const release = releases.find(release => { + // $FlowFixMe + return semver.satisfies(release.version, range); + }); + + if (!release) { + throw new Error(`Release not found: ${range}`); + } + + const asset = getBundleAsset(release); + invariant(asset, 'The bundle asset should exist'); + + bundleUrl = asset.browser_download_url; + bundleVersion = release.version.version; + } + + reporter.log(`Downloading ${chalk.green(bundleUrl)}...`); + + const bundle = await fetchBundle(config, bundleUrl); + const rc = getRcConfigForCwd(config.lockfileFolder, []); + + const yarnPath = path.resolve(config.lockfileFolder, `.yarn/releases/yarn-${bundleVersion}.js`); + reporter.log(`Saving it into ${chalk.magenta(yarnPath)}...`); + await fs.mkdirp(path.dirname(yarnPath)); + await fs.writeFile(yarnPath, bundle); + await fs.chmod(yarnPath, 0o755); + + const rcPath = `${config.lockfileFolder}/.yarnrc`; + reporter.log(`Updating ${chalk.magenta(rcPath)}...`); + rc['yarn-path'] = path.relative(config.lockfileFolder, yarnPath); + await fs.writeFilePreservingEol(rcPath, `${stringify(rc)}\n`); + + reporter.log(`Done!`); + }, +}); + +export {run, setFlags, examples}; diff --git a/src/registries/yarn-registry.js b/src/registries/yarn-registry.js index 020638afa5..5e63209129 100644 --- a/src/registries/yarn-registry.js +++ b/src/registries/yarn-registry.js @@ -31,7 +31,8 @@ export const DEFAULTS = { 'user-agent': [`yarn/${version}`, 'npm/?', `node/${process.version}`, process.platform, process.arch].join(' '), }; -const RELATIVE_KEYS = ['yarn-offline-mirror', 'cache-folder', 'offline-cache-folder']; +const RELATIVE_KEYS = ['yarn-offline-mirror', 'cache-folder', 'offline-cache-folder', 'yarn-path']; +const FOLDER_KEY = ['yarn-offline-mirror', 'cache-folder', 'offline-cache-folder']; const npmMap = { 'version-git-sign': 'sign-git-tag', @@ -96,7 +97,10 @@ export default class YarnRegistry extends NpmRegistry { if (!this.config[key] && valueLoc) { const resolvedLoc = (config[key] = path.resolve(path.dirname(loc), valueLoc)); - await fs.mkdirp(resolvedLoc); + + if (FOLDER_KEY.includes(key)) { + await fs.mkdirp(resolvedLoc); + } } } diff --git a/src/util/fs.js b/src/util/fs.js index 7877d0a63c..498b9707d2 100644 --- a/src/util/fs.js +++ b/src/util/fs.js @@ -28,7 +28,9 @@ export const lockQueue = new BlockingQueue('fs lock'); export const readFileBuffer = promisify(fs.readFile); export const open: (path: string, flags: string, mode?: number) => Promise> = promisify(fs.open); -export const writeFile: (path: string, data: string, options?: Object) => Promise = promisify(fs.writeFile); +export const writeFile: (path: string, data: string | Buffer, options?: Object) => Promise = promisify( + fs.writeFile, +); export const readlink: (path: string, opts: void) => Promise = promisify(fs.readlink); export const realpath: (path: string, opts: void) => Promise = promisify(fs.realpath); export const readdir: (path: string, opts: void) => Promise> = promisify(fs.readdir);