diff --git a/.changeset/pink-bags-push.md b/.changeset/pink-bags-push.md new file mode 100644 index 000000000000..37bcc0351b3c --- /dev/null +++ b/.changeset/pink-bags-push.md @@ -0,0 +1,7 @@ +--- +"wrangler": minor +--- + +feat: When Wrangler crashes, send an error report to Sentry to aid in debugging. + +When Wrangler's top-level exception handler catches an error thrown from Wrangler's application, it will offer to report the error to Sentry. This requires opt-in from the user every time. diff --git a/.github/workflows/create-pullrequest-prerelease.yml b/.github/workflows/create-pullrequest-prerelease.yml index 0075e383804e..c15e2db5da30 100644 --- a/.github/workflows/create-pullrequest-prerelease.yml +++ b/.github/workflows/create-pullrequest-prerelease.yml @@ -68,6 +68,7 @@ jobs: NODE_ENV: "production" ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }} ALGOLIA_PUBLIC_KEY: ${{ secrets.ALGOLIA_PUBLIC_KEY }} + SENTRY_DSN: "https://9edbb8417b284aa2bbead9b4c318918b@sentry10.cfdata.org/583" CI_OS: ${{ runner.os }} - name: Pack miniflare diff --git a/.github/workflows/prereleases.yml b/.github/workflows/prereleases.yml index ebf660851c2a..3a920be6ac67 100644 --- a/.github/workflows/prereleases.yml +++ b/.github/workflows/prereleases.yml @@ -67,6 +67,7 @@ jobs: # this is the "test/staging" key for sparrow analytics SPARROW_SOURCE_KEY: "5adf183f94b3436ba78d67f506965998" ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }} + SENTRY_DSN: "https://9edbb8417b284aa2bbead9b4c318918b@sentry10.cfdata.org/583" ALGOLIA_PUBLIC_KEY: ${{ secrets.ALGOLIA_PUBLIC_KEY }} working-directory: packages/wrangler @@ -111,6 +112,7 @@ jobs: NODE_ENV: "production" ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }} ALGOLIA_PUBLIC_KEY: ${{ secrets.ALGOLIA_PUBLIC_KEY }} + SENTRY_DSN: "https://9edbb8417b284aa2bbead9b4c318918b@sentry10.cfdata.org/583" CI_OS: ${{ runner.os }} - name: Build & Publish Prerelease Registry diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7888de18d00d..0d00aab997a4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -62,6 +62,8 @@ jobs: NPM_PUBLISH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }} ALGOLIA_PUBLIC_KEY: ${{ secrets.ALGOLIA_PUBLIC_KEY }} + SENTRY_DSN: "https://9edbb8417b284aa2bbead9b4c318918b@sentry10.cfdata.org/583" + NODE_ENV: "production" # This is the "production" key for sparrow analytics. # Include this here because this step will rebuild Wrangler and needs to have this available diff --git a/packages/wrangler/package.json b/packages/wrangler/package.json index a7f1b0e45266..6481bc3bed8d 100644 --- a/packages/wrangler/package.json +++ b/packages/wrangler/package.json @@ -123,10 +123,12 @@ "@cloudflare/pages-shared": "workspace:^", "@cloudflare/types": "^6.18.4", "@cloudflare/workers-tsconfig": "workspace:*", - "https-proxy-agent": "7.0.2", "@cloudflare/workers-types": "^4.20230914.0", "@iarna/toml": "^3.0.0", "@microsoft/api-extractor": "^7.28.3", + "@sentry/node": "^7.86.0", + "@sentry/types": "^7.86.0", + "@sentry/utils": "^7.86.0", "@types/body-parser": "^1.19.2", "@types/busboy": "^1.5.0", "@types/command-exists": "^1.2.0", @@ -164,6 +166,7 @@ "get-port": "^6.1.2", "glob-to-regexp": "0.4.1", "http-terminator": "^3.2.0", + "https-proxy-agent": "7.0.2", "ignore": "^5.2.0", "ink": "^3.2.0", "ink-select-input": "^4.2.1", diff --git a/packages/wrangler/scripts/bundle.ts b/packages/wrangler/scripts/bundle.ts index 11741c747079..ec6bcab6a6d7 100644 --- a/packages/wrangler/scripts/bundle.ts +++ b/packages/wrangler/scripts/bundle.ts @@ -54,6 +54,9 @@ async function buildMain(flags: BuildFlags = {}) { ...(process.env.ALGOLIA_PUBLIC_KEY ? { ALGOLIA_PUBLIC_KEY: `"${process.env.ALGOLIA_PUBLIC_KEY}"` } : {}), + ...(process.env.SENTRY_DSN + ? { SENTRY_DSN: `"${process.env.SENTRY_DSN}"` } + : {}), }, plugins: [embedWorkersPlugin], }; diff --git a/packages/wrangler/src/__tests__/deployments.test.ts b/packages/wrangler/src/__tests__/deployments.test.ts index 1729ab8c65a4..c1dd0a5195ba 100644 --- a/packages/wrangler/src/__tests__/deployments.test.ts +++ b/packages/wrangler/src/__tests__/deployments.test.ts @@ -301,7 +301,7 @@ describe("deployments", () => { "🚧\`wrangler rollback\` is a beta command. Please report any issues to https://github.com/cloudflare/workers-sdk/issues/new/choose ? This deployment 3mEgaU1T will immediately replace the current deployment and become the active deployment across all your deployed routes and domains. However, your local development environment will not be affected by this rollback. Note: Rolling back to a previous deployment will not rollback any of the bound resources (Durable Object, R2, KV, etc.). - 🤖 Using default value in non-interactive context: yes + 🤖 Using fallback value in non-interactive context: yes ? Please provide a message for this rollback (120 characters max) 🤖 Using default value in non-interactive context: diff --git a/packages/wrangler/src/__tests__/sentry.test.ts b/packages/wrangler/src/__tests__/sentry.test.ts new file mode 100644 index 000000000000..706b5227fd00 --- /dev/null +++ b/packages/wrangler/src/__tests__/sentry.test.ts @@ -0,0 +1,109 @@ +import { rest } from "msw"; + +import { mockAccountId, mockApiToken } from "./helpers/mock-account-id"; +import { mockConsoleMethods } from "./helpers/mock-console"; +import { clearDialogs, mockConfirm } from "./helpers/mock-dialogs"; +import { useMockIsTTY } from "./helpers/mock-istty"; +import { msw } from "./helpers/msw"; +import { runInTempDir } from "./helpers/run-in-tmp"; +import { runWrangler } from "./helpers/run-wrangler"; + +declare const global: { SENTRY_DSN: string | undefined }; + +describe("sentry", () => { + const ORIGINAL_SENTRY_DSN = global.SENTRY_DSN; + const std = mockConsoleMethods(); + runInTempDir(); + mockAccountId(); + mockApiToken(); + const { setIsTTY } = useMockIsTTY(); + + let sentryRequests: { count: number } | undefined; + + beforeEach(() => { + global.SENTRY_DSN = + "https://9edbb8417b284aa2bbead9b4c318918b@sentry.example.com/24601"; + + sentryRequests = mockSentryEndpoint(); + }); + afterEach(() => { + global.SENTRY_DSN = ORIGINAL_SENTRY_DSN; + clearDialogs(); + msw.resetHandlers(); + }); + describe("non interactive", () => { + it("should not hit sentry in normal usage", async () => { + await runWrangler("version"); + expect(sentryRequests?.count).toEqual(0); + }); + + it("should not hit sentry after error", async () => { + await expect(runWrangler("delete")).rejects.toMatchInlineSnapshot( + `[AssertionError: A worker name must be defined, either via --name, or in wrangler.toml]` + ); + expect(std.out).toMatchInlineSnapshot(` + " + If you think this is a bug then please create an issue at https://github.com/cloudflare/workers-sdk/issues/new/choose + ? Would you like to report this error to Cloudflare? + 🤖 Using fallback value in non-interactive context: no" + `); + expect(sentryRequests?.count).toEqual(0); + }); + }); + describe("interactive", () => { + beforeEach(() => { + setIsTTY(true); + }); + afterEach(() => { + setIsTTY(false); + }); + it("should not hit sentry in normal usage", async () => { + await runWrangler("version"); + expect(sentryRequests?.count).toEqual(0); + }); + it("should not hit sentry after error when permission denied", async () => { + mockConfirm({ + text: "Would you like to report this error to Cloudflare?", + result: false, + }); + await expect(runWrangler("delete")).rejects.toMatchInlineSnapshot( + `[AssertionError: A worker name must be defined, either via --name, or in wrangler.toml]` + ); + expect(std.out).toMatchInlineSnapshot(` + " + If you think this is a bug then please create an issue at https://github.com/cloudflare/workers-sdk/issues/new/choose" + `); + expect(sentryRequests?.count).toEqual(0); + }); + it("should hit sentry after error when permission provided", async () => { + mockConfirm({ + text: "Would you like to report this error to Cloudflare?", + result: true, + }); + await expect(runWrangler("delete")).rejects.toMatchInlineSnapshot( + `[AssertionError: A worker name must be defined, either via --name, or in wrangler.toml]` + ); + expect(std.out).toMatchInlineSnapshot(` + " + If you think this is a bug then please create an issue at https://github.com/cloudflare/workers-sdk/issues/new/choose" + `); + // Sentry sends multiple HTTP requests to capture breadcrumbs + expect(sentryRequests?.count).toBeGreaterThan(0); + }); + }); +}); + +function mockSentryEndpoint() { + const requests = { count: 0 }; + msw.use( + rest.post( + `https://platform.dash.cloudflare.com/sentry/envelope`, + async (req, res, cxt) => { + requests.count++; + return res(cxt.status(200), cxt.json({})); + } + ) + ); + + return requests; +} diff --git a/packages/wrangler/src/dialogs.ts b/packages/wrangler/src/dialogs.ts index 6b0f1df987f4..a735005cdb23 100644 --- a/packages/wrangler/src/dialogs.ts +++ b/packages/wrangler/src/dialogs.ts @@ -21,20 +21,21 @@ export class NoDefaultValueProvided extends Error { interface ConfirmOptions { defaultValue?: boolean; + fallbackValue?: boolean; } export async function confirm( text: string, - { defaultValue = true }: ConfirmOptions = {} + { defaultValue = true, fallbackValue = true }: ConfirmOptions = {} ): Promise { if (isNonInteractiveOrCI()) { logger.log(`? ${text}`); logger.log( `🤖 ${chalk.dim( - "Using default value in non-interactive context:" - )} ${chalk.white.bold(defaultValue ? "yes" : "no")}` + "Using fallback value in non-interactive context:" + )} ${chalk.white.bold(fallbackValue ? "yes" : "no")}` ); - return defaultValue; + return fallbackValue; } const { value } = await prompts({ type: "confirm", diff --git a/packages/wrangler/src/index.ts b/packages/wrangler/src/index.ts index c01c6e518d51..0d03e59156be 100644 --- a/packages/wrangler/src/index.ts +++ b/packages/wrangler/src/index.ts @@ -39,6 +39,7 @@ import { initHandler, initOptions } from "./init"; import { kvNamespace, kvKey, kvBulk } from "./kv"; import { logBuildFailure, logger } from "./logger"; import * as metrics from "./metrics"; + import { mTlsCertificateCommands } from "./mtls-certificate/cli"; import { pages } from "./pages"; import { formatMessage, ParseError } from "./parse"; @@ -46,6 +47,12 @@ import { pubSubCommands } from "./pubsub/pubsub-commands"; import { queues } from "./queues/cli/commands"; import { r2 } from "./r2"; import { secret, secretBulkHandler, secretBulkOptions } from "./secret"; +import { + captureGlobalException, + addBreadcrumb, + closeSentry, + setupSentry, +} from "./sentry"; import { tailOptions, tailHandler } from "./tail"; import { generateTypes } from "./type-generation"; import { printWranglerBanner } from "./update-check"; @@ -701,6 +708,9 @@ export function createCLIParser(argv: string[]) { } export async function main(argv: string[]): Promise { + setupSentry(); + addBreadcrumb(`wrangler ${argv.join(" ")}`); + const wrangler = createCLIParser(argv); try { await wrangler.parse(); @@ -755,9 +765,11 @@ export async function main(argv: string[]): Promise { `${fgGreenColor}%s${resetColor}`, "If you think this is a bug then please create an issue at https://github.com/cloudflare/workers-sdk/issues/new/choose" ); + await captureGlobalException(e); } throw e; } finally { + await closeSentry(); // In the bootstrapper script `bin/wrangler.js`, we open an IPC channel, so // IPC messages from this process are propagated through the bootstrapper. // Make sure this channel is closed once it's no longer needed, so we can diff --git a/packages/wrangler/src/sentry/index.ts b/packages/wrangler/src/sentry/index.ts new file mode 100644 index 000000000000..56b89fdb24d5 --- /dev/null +++ b/packages/wrangler/src/sentry/index.ts @@ -0,0 +1,132 @@ +import * as Sentry from "@sentry/node"; +import { rejectedSyncPromise } from "@sentry/utils"; +import { fetch } from "undici"; +import { version as wranglerVersion } from "../../package.json"; +import { confirm } from "../dialogs"; +import { logger } from "../logger"; +import type { BaseTransportOptions, TransportRequest } from "@sentry/types"; +import type { RequestInit } from "undici"; + +let sentryReportingAllowed = false; + +// The SENTRY_DSN is provided at esbuild time as a `define` for production and beta releases. +// Otherwise it is left undefined, which disables reporting. +declare const SENTRY_DSN: string; + +/* Returns a Sentry transport for the Sentry proxy Worker. */ +export const makeSentry10Transport = (options: BaseTransportOptions) => { + let eventQueue: [string, RequestInit][] = []; + + const transportSentry10 = async (request: TransportRequest) => { + /* Adds helpful properties to the request body before we send it to our + proxy Worker. These properties can be parsed out from the NDJSON in + `request.body`, but it's easier and safer to just attach them here. */ + const sentryWorkerPayload = { + envelope: request.body, + url: options.url, + }; + + try { + if (sentryReportingAllowed) { + const eventsToSend = [...eventQueue]; + eventQueue = []; + for (const event of eventsToSend) { + await fetch(event[0], event[1]); + } + + const response = await fetch( + `https://platform.dash.cloudflare.com/sentry/envelope`, + { + method: "POST", + headers: { + Accept: "*/*", + "Content-Type": "application/json", + }, + body: JSON.stringify(sentryWorkerPayload), + } + ); + + return { + statusCode: response.status, + headers: { + "x-sentry-rate-limits": response.headers.get( + "X-Sentry-Rate-Limits" + ), + "retry-after": response.headers.get("Retry-After"), + }, + }; + } else { + // We don't currently have permission to send this event, but maybe we will in the future. + // Add to an in-memory just in case + eventQueue.push([ + `https://platform.dash.cloudflare.com/sentry/envelope`, + { + method: "POST", + headers: { + Accept: "*/*", + "Content-Type": "application/json", + }, + body: JSON.stringify(sentryWorkerPayload), + }, + ]); + return { + statusCode: 200, + }; + } + } catch (err) { + console.log(err); + + return rejectedSyncPromise(err); + } + }; + + return Sentry.createTransport(options, transportSentry10); +}; + +export function setupSentry() { + if (typeof SENTRY_DSN !== "undefined") { + Sentry.init({ + release: `wrangler@${wranglerVersion}`, + dsn: SENTRY_DSN, + transport: makeSentry10Transport, + }); + } +} + +export function addBreadcrumb( + message: string, + level: Sentry.SeverityLevel = "log" +) { + if (typeof SENTRY_DSN !== "undefined") { + Sentry.addBreadcrumb({ + message, + level, + }); + } +} + +// Capture top-level Wrangler errors. Also take this opportunity to ask the user for +// consent if not already granted. +export async function captureGlobalException(e: unknown) { + if (typeof SENTRY_DSN !== "undefined") { + sentryReportingAllowed = await confirm( + "Would you like to report this error to Cloudflare?", + { fallbackValue: false } + ); + + if (!sentryReportingAllowed) { + logger.debug(`Sentry: Reporting disabled - would have sent ${e}.`); + return; + } + + logger.debug(`Sentry: Capturing exception ${e}`); + Sentry.captureException(e); + } +} + +// Ensure we send Sentry events before Wrangler exits +export async function closeSentry() { + if (typeof SENTRY_DSN !== "undefined") { + await Sentry.close(); + } +} diff --git a/packages/wrangler/turbo.json b/packages/wrangler/turbo.json index cc9811eddf0a..f685138e64df 100644 --- a/packages/wrangler/turbo.json +++ b/packages/wrangler/turbo.json @@ -37,7 +37,8 @@ "JEST_WORKER_ID", "http_proxy", "HTTP_PROXY", - "CI_OS" + "CI_OS", + "SENTRY_DSN" ] } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c093aec524fa..fd1c171be6af 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -736,7 +736,7 @@ importers: version: 8.49.0 eslint-config-turbo: specifier: latest - version: 1.10.16(eslint@8.49.0) + version: 1.10.15(eslint@8.49.0) eslint-plugin-import: specifier: 2.26.x version: 2.26.0(@typescript-eslint/parser@6.7.2)(eslint@8.49.0) @@ -1305,6 +1305,15 @@ importers: '@microsoft/api-extractor': specifier: ^7.28.3 version: 7.28.3 + '@sentry/node': + specifier: ^7.86.0 + version: 7.86.0(supports-color@9.2.2) + '@sentry/types': + specifier: ^7.86.0 + version: 7.86.0 + '@sentry/utils': + specifier: ^7.86.0 + version: 7.86.0 '@types/body-parser': specifier: ^1.19.2 version: 1.19.2 @@ -5826,6 +5835,15 @@ packages: string-argv: 0.3.1 dev: true + /@sentry-internal/tracing@7.86.0: + resolution: {integrity: sha512-b4dUsNWlPWRwakGwR7bhOkqiFlqQszH1hhVFwrm/8s3kqEBZ+E4CeIfCvuHBHQ1cM/fx55xpXX/BU163cy+3iQ==} + engines: {node: '>=8'} + dependencies: + '@sentry/core': 7.86.0 + '@sentry/types': 7.86.0 + '@sentry/utils': 7.86.0 + dev: true + /@sentry/core@7.64.0: resolution: {integrity: sha512-IzmEyl5sNG7NyEFiyFHEHC+sizsZp9MEw1+RJRLX6U5RITvcsEgcajSkHQFafaBPzRrcxZMdm47Cwhl212LXcw==} engines: {node: '>=8'} @@ -5844,6 +5862,14 @@ packages: tslib: 2.5.3 dev: true + /@sentry/core@7.86.0: + resolution: {integrity: sha512-SbLvqd1bRYzhDS42u7GMnmbDMfth/zRiLElQWbLK/shmuZzTcfQSwNNdF4Yj+VfjOkqPFgGmICHSHVUc9dh01g==} + engines: {node: '>=8'} + dependencies: + '@sentry/types': 7.86.0 + '@sentry/utils': 7.86.0 + dev: true + /@sentry/integrations@7.64.0: resolution: {integrity: sha512-6gbSGiruOifAmLtXw//Za19GWiL5qugDMEFxSvc5WrBWb+A8UK+foPn3K495OcivLS68AmqAQCUGb+6nlVowwA==} engines: {node: '>=8'} @@ -5864,6 +5890,19 @@ packages: tslib: 2.5.3 dev: true + /@sentry/node@7.86.0(supports-color@9.2.2): + resolution: {integrity: sha512-cB1bn/LMn2Km97Y3hv63xwWxT50/G5ixGuSxTZ3dCQM6VDhmZoCuC5NGT3itVvaRd6upQXRZa5W0Zgyh0HXKig==} + engines: {node: '>=8'} + dependencies: + '@sentry-internal/tracing': 7.86.0 + '@sentry/core': 7.86.0 + '@sentry/types': 7.86.0 + '@sentry/utils': 7.86.0 + https-proxy-agent: 5.0.1(supports-color@9.2.2) + transitivePeerDependencies: + - supports-color + dev: true + /@sentry/types@7.64.0: resolution: {integrity: sha512-LqjQprWXjUFRmzIlUjyA+KL+38elgIYmAeoDrdyNVh8MK5IC1W2Lh1Q87b4yOiZeMiIhIVNBd7Ecoh2rodGrGA==} engines: {node: '>=8'} @@ -5874,6 +5913,11 @@ packages: engines: {node: '>=8'} dev: true + /@sentry/types@7.86.0: + resolution: {integrity: sha512-pGAt0+bMfWgo0KG2epthfNV4Wae03tURpoxNjGo5Fr4cXxvLTSijSAQ6rmmO4bXBJ7+rErEjX30g30o/eEdP9g==} + engines: {node: '>=8'} + dev: true + /@sentry/utils@7.64.0: resolution: {integrity: sha512-HRlM1INzK66Gt+F4vCItiwGKAng4gqzCR4C5marsL3qv6SrKH98dQnCGYgXluSWaaa56h97FRQu7TxCk6jkSvQ==} engines: {node: '>=8'} @@ -5890,6 +5934,13 @@ packages: tslib: 2.5.3 dev: true + /@sentry/utils@7.86.0: + resolution: {integrity: sha512-6PejFtw9VTFFy5vu0ks+U7Ozkqz+eMt+HN8AZKBKErYzX5/xs0kpkOcSRpu3ETdTYcZf8VAmLVgFgE2BE+3WuQ==} + engines: {node: '>=8'} + dependencies: + '@sentry/types': 7.86.0 + dev: true + /@sinclair/typebox@0.27.8: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} @@ -7468,7 +7519,7 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - /agent-base@6.0.2: + /agent-base@6.0.2(supports-color@9.2.2): resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} dependencies: @@ -9936,13 +9987,13 @@ packages: eslint: 8.49.0 dev: true - /eslint-config-turbo@1.10.16(eslint@8.49.0): - resolution: {integrity: sha512-O3NQI72bQHV7FvSC6lWj66EGx8drJJjuT1kuInn6nbMLOHdMBhSUX/8uhTAlHRQdlxZk2j9HtgFCIzSc93w42g==} + /eslint-config-turbo@1.10.15(eslint@8.49.0): + resolution: {integrity: sha512-76mpx2x818JZE26euen14utYcFDxOahZ9NaWA+6Xa4pY2ezVKVschuOxS96EQz3o3ZRSmcgBOapw/gHbN+EKxQ==} peerDependencies: eslint: '>6.6.0' dependencies: eslint: 8.49.0 - eslint-plugin-turbo: 1.10.16(eslint@8.49.0) + eslint-plugin-turbo: 1.10.15(eslint@8.49.0) dev: false /eslint-import-resolver-node@0.3.7: @@ -10369,8 +10420,8 @@ packages: - typescript dev: true - /eslint-plugin-turbo@1.10.16(eslint@8.49.0): - resolution: {integrity: sha512-ZjrR88MTN64PNGufSEcM0tf+V1xFYVbeiMeuIqr0aiABGomxFLo4DBkQ7WI4WzkZtWQSIA2sP+yxqSboEfL9MQ==} + /eslint-plugin-turbo@1.10.15(eslint@8.49.0): + resolution: {integrity: sha512-Tv4QSKV/U56qGcTqS/UgOvb9HcKFmWOQcVh3HEaj7of94lfaENgfrtK48E2CckQf7amhKs1i+imhCsNCKjkQyA==} peerDependencies: eslint: '>6.6.0' dependencies: @@ -11710,7 +11761,7 @@ packages: engines: {node: '>= 6'} dependencies: '@tootallnate/once': 1.1.2 - agent-base: 6.0.2 + agent-base: 6.0.2(supports-color@9.2.2) debug: 4.3.4(supports-color@9.2.2) transitivePeerDependencies: - supports-color @@ -11744,11 +11795,11 @@ packages: resolve-alpn: 1.2.1 dev: true - /https-proxy-agent@5.0.1: + /https-proxy-agent@5.0.1(supports-color@9.2.2): resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} dependencies: - agent-base: 6.0.2 + agent-base: 6.0.2(supports-color@9.2.2) debug: 4.3.4(supports-color@9.2.2) transitivePeerDependencies: - supports-color @@ -14936,11 +14987,11 @@ packages: engines: {node: '>= 8'} dependencies: '@tootallnate/once': 1.1.2 - agent-base: 6.0.2 + agent-base: 6.0.2(supports-color@9.2.2) debug: 4.3.4(supports-color@9.2.2) get-uri: 3.0.2 http-proxy-agent: 4.0.1 - https-proxy-agent: 5.0.1 + https-proxy-agent: 5.0.1(supports-color@9.2.2) pac-resolver: 5.0.1 raw-body: 2.5.1 socks-proxy-agent: 5.0.1 @@ -15506,10 +15557,10 @@ packages: resolution: {integrity: sha512-gkH7BkvLVkSfX9Dk27W6TyNOWWZWRilRfk1XxGNWOYJ2TuedAv1yFpCaU9QSBmBe716XOTNpYNOzhysyw8xn7g==} engines: {node: '>= 8'} dependencies: - agent-base: 6.0.2 + agent-base: 6.0.2(supports-color@9.2.2) debug: 4.3.4(supports-color@9.2.2) http-proxy-agent: 4.0.1 - https-proxy-agent: 5.0.1 + https-proxy-agent: 5.0.1(supports-color@9.2.2) lru-cache: 5.1.1 pac-proxy-agent: 5.0.0 proxy-from-env: 1.1.0 @@ -16748,7 +16799,7 @@ packages: resolution: {integrity: sha512-vZdmnjb9a2Tz6WEQVIurybSwElwPxMZaIc7PzqbJTrezcKNznv6giT7J7tZDZ1BojVaa1jvO/UiUdhDVB0ACoQ==} engines: {node: '>= 6'} dependencies: - agent-base: 6.0.2 + agent-base: 6.0.2(supports-color@9.2.2) debug: 4.3.4(supports-color@9.2.2) socks: 2.7.1 transitivePeerDependencies: