Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implements "yarn policies set-version" #6673

Merged
merged 5 commits into from Nov 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions src/cli/commands/index.js
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down
160 changes: 160 additions & 0 deletions 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<ReleaseAsset>,
|};

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<Array<Release>> {
const request: Array<Release> = 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<Buffer> {
return config.requestManager.request({
url,
buffer: true,
});
}

export function hasWrapper(flags: Object, args: Array<string>): boolean {
return false;
}

const {run, setFlags, examples} = buildSubCommands('policies', {
async setVersion(config: Config, reporter: Reporter, flags: Object, args: Array<string>): Promise<void> {
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};
8 changes: 6 additions & 2 deletions src/registries/yarn-registry.js
Expand Up @@ -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',
Expand Down Expand Up @@ -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);
}
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/util/fs.js
Expand Up @@ -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<Array<string>> = promisify(fs.open);
export const writeFile: (path: string, data: string, options?: Object) => Promise<void> = promisify(fs.writeFile);
export const writeFile: (path: string, data: string | Buffer, options?: Object) => Promise<void> = promisify(
fs.writeFile,
);
export const readlink: (path: string, opts: void) => Promise<string> = promisify(fs.readlink);
export const realpath: (path: string, opts: void) => Promise<string> = promisify(fs.realpath);
export const readdir: (path: string, opts: void) => Promise<Array<string>> = promisify(fs.readdir);
Expand Down