Skip to content

Commit

Permalink
Make the local cache opt-in (rather than opt-out) (#4252)
Browse files Browse the repository at this point in the history
* Disables the local cache by default

* Fixes linting

* Versions
  • Loading branch information
arcanis committed Mar 24, 2022
1 parent e143651 commit ea699bc
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 19 deletions.
33 changes: 33 additions & 0 deletions .yarn/versions/b2f24cb2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
releases:
"@yarnpkg/cli": major
"@yarnpkg/core": major
"@yarnpkg/plugin-npm": minor

declined:
- "@yarnpkg/plugin-compat"
- "@yarnpkg/plugin-constraints"
- "@yarnpkg/plugin-dlx"
- "@yarnpkg/plugin-essentials"
- "@yarnpkg/plugin-exec"
- "@yarnpkg/plugin-file"
- "@yarnpkg/plugin-git"
- "@yarnpkg/plugin-github"
- "@yarnpkg/plugin-http"
- "@yarnpkg/plugin-init"
- "@yarnpkg/plugin-interactive-tools"
- "@yarnpkg/plugin-link"
- "@yarnpkg/plugin-nm"
- "@yarnpkg/plugin-npm-cli"
- "@yarnpkg/plugin-pack"
- "@yarnpkg/plugin-patch"
- "@yarnpkg/plugin-pnp"
- "@yarnpkg/plugin-pnpm"
- "@yarnpkg/plugin-stage"
- "@yarnpkg/plugin-typescript"
- "@yarnpkg/plugin-version"
- "@yarnpkg/plugin-workspace-tools"
- "@yarnpkg/builder"
- "@yarnpkg/doctor"
- "@yarnpkg/nm"
- "@yarnpkg/pnpify"
- "@yarnpkg/sdks"
11 changes: 11 additions & 0 deletions packages/gatsby/content/advanced/lexicon.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ See also: the [`Linker` interface](https://github.com/yarnpkg/berry/blob/master/

See also: the [`Installer` interface](https://github.com/yarnpkg/berry/blob/master/packages/yarnpkg-core/sources/Installer.ts#L18)

### Local Cache

The local cache is a way to protect against registries going down by keeping a cache of your packages within your very project (often checked-in within the repository). While not always practical (it causes the repository size to grow, although we have ways to mitigate it significantly), it presents various interesting properties:

- It doesn't require additional infrastructure, such as a [Verdaccio proxy](https://verdaccio.org/)
- It doesn't require additional configuration, such as registry authentication
- The install fetch step is as fast as it can be, with no data transfer at all
- It lets you reach [zero-installs](https://yarnpkg.com/features/zero-installs) if you also use the PnP linker

To enable the local cache, set [`enableGlobalCache`](/configuration/yarnrc#enableGlobalCache) to `false`, run an install, and add the new artifacts to your repository (you might want to [update your gitignore](/getting-started/qa#which-files-should-be-gitignored) accordingly).

### Locator

A locator is a combination of a package name (for example `lodash`) and a package <abbr>reference</abbr> (for example `1.2.3`). Locators are used to identify a single unique package (interestingly, all valid locators also are valid <abbr>descriptors</abbr>).
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-npm/sources/NpmHttpFetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export class NpmHttpFetcher implements Fetcher {
throw new Error(`Assertion failed: The archiveUrl querystring parameter should have been available`);

const sourceBuffer = await npmHttpUtils.get(params.__archiveUrl, {
customErrorMessage: npmHttpUtils.customPackageError,
configuration: opts.project.configuration,
ident: locator,
});
Expand Down
2 changes: 2 additions & 0 deletions packages/plugin-npm/sources/NpmSemverFetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export class NpmSemverFetcher implements Fetcher {
let sourceBuffer;
try {
sourceBuffer = await npmHttpUtils.get(NpmSemverFetcher.getLocatorUrl(locator), {
customErrorMessage: npmHttpUtils.customPackageError,
configuration: opts.project.configuration,
ident: locator,
});
Expand All @@ -58,6 +59,7 @@ export class NpmSemverFetcher implements Fetcher {
// OK: https://registry.yarnpkg.com/@emotion%2fbabel-preset-css-prop/-/babel-preset-css-prop-10.0.7.tgz
// KO: https://registry.yarnpkg.com/@xtuc%2fieee754/-/ieee754-1.2.0.tgz
sourceBuffer = await npmHttpUtils.get(NpmSemverFetcher.getLocatorUrl(locator).replace(/%2f/g, `/`), {
customErrorMessage: npmHttpUtils.customPackageError,
configuration: opts.project.configuration,
ident: locator,
});
Expand Down
2 changes: 2 additions & 0 deletions packages/plugin-npm/sources/NpmSemverResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export class NpmSemverResolver implements Resolver {
throw new Error(`Expected a valid range, got ${descriptor.range.slice(PROTOCOL.length)}`);

const registryData = await npmHttpUtils.get(npmHttpUtils.getIdentUrl(descriptor), {
customErrorMessage: npmHttpUtils.customPackageError,
configuration: opts.project.configuration,
ident: descriptor,
jsonResponse: true,
Expand Down Expand Up @@ -118,6 +119,7 @@ export class NpmSemverResolver implements Resolver {
throw new ReportError(MessageName.RESOLVER_NOT_FOUND, `The npm semver resolver got selected, but the version isn't semver`);

const registryData = await npmHttpUtils.get(npmHttpUtils.getIdentUrl(locator), {
customErrorMessage: npmHttpUtils.customPackageError,
configuration: opts.project.configuration,
ident: locator,
jsonResponse: true,
Expand Down
28 changes: 19 additions & 9 deletions packages/plugin-npm/sources/npmHttpUtils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {Configuration, Ident, httpUtils} from '@yarnpkg/core';
import {MessageName, ReportError} from '@yarnpkg/core';
import {prompt} from 'enquirer';
import {URL} from 'url';
import {Configuration, Ident, formatUtils, httpUtils} from '@yarnpkg/core';
import {MessageName, ReportError} from '@yarnpkg/core';
import {prompt} from 'enquirer';
import {URL} from 'url';

import {Hooks} from './index';
import * as npmConfigUtils from './npmConfigUtils';
import {MapLike} from './npmConfigUtils';
import {Hooks} from './index';
import * as npmConfigUtils from './npmConfigUtils';
import {MapLike} from './npmConfigUtils';

export enum AuthType {
NO_AUTH,
Expand Down Expand Up @@ -42,8 +42,18 @@ export async function handleInvalidAuthenticationError(error: any, {attemptedAs,
}
}

export function customPackageError(error: httpUtils.RequestError) {
return error.response?.statusCode === 404 ? `Package not found` : null;
export function customPackageError(error: httpUtils.RequestError, configuration: Configuration) {
const statusCode = error.response?.statusCode;
if (!statusCode)
return null;

if (statusCode === 404)
return `Package not found`;

if (statusCode >= 500 && statusCode < 600)
return `The registry appears to be down (using a ${formatUtils.applyHyperlink(configuration, `local cache`, `https://yarnpkg.com/advanced/lexicon#local-cache`)} might have protected you against such outages)`;

return null;
}

export function getIdentUrl(ident: Ident) {
Expand Down
2 changes: 1 addition & 1 deletion packages/yarnpkg-core/sources/Configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ export const coreDefinitions: {[coreSettingName: string]: SettingsDefinition} =
enableGlobalCache: {
description: `If true, the system-wide cache folder will be used regardless of \`cache-folder\``,
type: SettingsType.BOOLEAN,
default: false,
default: true,
},

// Settings related to the output style
Expand Down
18 changes: 9 additions & 9 deletions packages/yarnpkg-core/sources/httpUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@ function prettyResponseCode({statusCode, statusMessage}: Response, configuration
return formatUtils.applyHyperlink(configuration, `${prettyStatusCode}${statusMessage ? ` (${statusMessage})` : ``}`, href);
}

async function prettyNetworkError(response: Promise<Response<any>>, {configuration, customErrorMessage}: {configuration: Configuration, customErrorMessage?: (err: RequestError) => string | null}) {
async function prettyNetworkError(response: Promise<Response<any>>, {configuration, customErrorMessage}: {configuration: Configuration, customErrorMessage?: (err: RequestError, configuration: Configuration) => string | null}) {
try {
return await response;
} catch (err) {
if (err.name !== `HTTPError`)
throw err;

let message = customErrorMessage?.(err) ?? err.response.body?.error;
let message = customErrorMessage?.(err, configuration) ?? err.response.body?.error;

if (message == null) {
if (err.message.startsWith(`Response code`)) {
Expand All @@ -65,7 +65,7 @@ async function prettyNetworkError(response: Promise<Response<any>>, {configurati
}

if (err instanceof TimeoutError && err.event === `socket`)
message += `(can be increased via ${formatUtils.pretty(configuration, `httpTimeout`, formatUtils.Type.SETTING)})`;
message += ` (can be increased via ${formatUtils.pretty(configuration, `httpTimeout`, formatUtils.Type.SETTING)})`;

const networkError = new ReportError(MessageName.NETWORK_ERROR, message, report => {
if (err.response) {
Expand Down Expand Up @@ -166,7 +166,7 @@ export enum Method {

export type Options = {
configuration: Configuration;
customErrorMessage?: (err: RequestError) => string | null;
customErrorMessage?: (err: RequestError, configuration: Configuration) => string | null;
headers?: {[headerName: string]: string};
jsonRequest?: boolean;
jsonResponse?: boolean;
Expand All @@ -183,9 +183,9 @@ export async function request(target: string | URL, body: Body, {configuration,
return await executor();
}

export async function get(target: string, {configuration, jsonResponse, ...rest}: Options) {
export async function get(target: string, {configuration, jsonResponse, customErrorMessage, ...rest}: Options) {
let entry = miscUtils.getFactoryWithDefault(cache, target, () => {
return prettyNetworkError(request(target, null, {configuration, ...rest}), {configuration}).then(response => {
return prettyNetworkError(request(target, null, {configuration, ...rest}), {configuration, customErrorMessage}).then(response => {
cache.set(target, response.body);
return response.body;
});
Expand All @@ -202,19 +202,19 @@ export async function get(target: string, {configuration, jsonResponse, ...rest}
}

export async function put(target: string, body: Body, {customErrorMessage, ...options}: Options): Promise<Buffer> {
const response = await prettyNetworkError(request(target, body, {...options, method: Method.PUT}), options);
const response = await prettyNetworkError(request(target, body, {...options, method: Method.PUT}), {customErrorMessage, configuration: options.configuration});

return response.body;
}

export async function post(target: string, body: Body, {customErrorMessage, ...options}: Options): Promise<Buffer> {
const response = await prettyNetworkError(request(target, body, {...options, method: Method.POST}), options);
const response = await prettyNetworkError(request(target, body, {...options, method: Method.POST}), {customErrorMessage, configuration: options.configuration});

return response.body;
}

export async function del(target: string, {customErrorMessage, ...options}: Options): Promise<Buffer> {
const response = await prettyNetworkError(request(target, null, {...options, method: Method.DELETE}), options);
const response = await prettyNetworkError(request(target, null, {...options, method: Method.DELETE}), {customErrorMessage, configuration: options.configuration});

return response.body;
}
Expand Down

0 comments on commit ea699bc

Please sign in to comment.