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; }