diff --git a/.yarn/versions/b2f24cb2.yml b/.yarn/versions/b2f24cb2.yml
new file mode 100644
index 000000000000..69c97570066e
--- /dev/null
+++ b/.yarn/versions/b2f24cb2.yml
@@ -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"
diff --git a/packages/gatsby/content/advanced/lexicon.md b/packages/gatsby/content/advanced/lexicon.md
index 776a7fbf07a6..07e47c5c670f 100644
--- a/packages/gatsby/content/advanced/lexicon.md
+++ b/packages/gatsby/content/advanced/lexicon.md
@@ -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 reference (for example `1.2.3`). Locators are used to identify a single unique package (interestingly, all valid locators also are valid descriptors).
diff --git a/packages/plugin-npm/sources/NpmHttpFetcher.ts b/packages/plugin-npm/sources/NpmHttpFetcher.ts
index 9873d538af96..d211b37ca27a 100644
--- a/packages/plugin-npm/sources/NpmHttpFetcher.ts
+++ b/packages/plugin-npm/sources/NpmHttpFetcher.ts
@@ -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,
});
diff --git a/packages/plugin-npm/sources/NpmSemverFetcher.ts b/packages/plugin-npm/sources/NpmSemverFetcher.ts
index 0f69423c7a0e..86afac506823 100644
--- a/packages/plugin-npm/sources/NpmSemverFetcher.ts
+++ b/packages/plugin-npm/sources/NpmSemverFetcher.ts
@@ -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,
});
@@ -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,
});
diff --git a/packages/plugin-npm/sources/NpmSemverResolver.ts b/packages/plugin-npm/sources/NpmSemverResolver.ts
index 426b470fffcb..609cf4d17f57 100644
--- a/packages/plugin-npm/sources/NpmSemverResolver.ts
+++ b/packages/plugin-npm/sources/NpmSemverResolver.ts
@@ -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,
@@ -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,
diff --git a/packages/plugin-npm/sources/npmHttpUtils.ts b/packages/plugin-npm/sources/npmHttpUtils.ts
index 390d359edf30..17bcb6b53ce9 100644
--- a/packages/plugin-npm/sources/npmHttpUtils.ts
+++ b/packages/plugin-npm/sources/npmHttpUtils.ts
@@ -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,
@@ -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) {
diff --git a/packages/yarnpkg-core/sources/Configuration.ts b/packages/yarnpkg-core/sources/Configuration.ts
index ab93467428b6..41a4d2aa4253 100644
--- a/packages/yarnpkg-core/sources/Configuration.ts
+++ b/packages/yarnpkg-core/sources/Configuration.ts
@@ -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
diff --git a/packages/yarnpkg-core/sources/httpUtils.ts b/packages/yarnpkg-core/sources/httpUtils.ts
index e6befc150148..ed42ef070c34 100644
--- a/packages/yarnpkg-core/sources/httpUtils.ts
+++ b/packages/yarnpkg-core/sources/httpUtils.ts
@@ -47,14 +47,14 @@ function prettyResponseCode({statusCode, statusMessage}: Response, configuration
return formatUtils.applyHyperlink(configuration, `${prettyStatusCode}${statusMessage ? ` (${statusMessage})` : ``}`, href);
}
-async function prettyNetworkError(response: Promise>, {configuration, customErrorMessage}: {configuration: Configuration, customErrorMessage?: (err: RequestError) => string | null}) {
+async function prettyNetworkError(response: Promise>, {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`)) {
@@ -65,7 +65,7 @@ async function prettyNetworkError(response: Promise>, {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) {
@@ -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;
@@ -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;
});
@@ -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 {
- 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 {
- 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 {
- 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;
}