diff --git a/CHANGELOG.md b/CHANGELOG.md index b9bd0710b8..dbfec9bd2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,9 @@ The following changes only apply when using `sentry-cli` via the npm package [`@ - Fixed misleading error message claiming the server doesn't support chunk uploading when the actual error was a non-existent organization ([#2930](https://github.com/getsentry/sentry-cli/pull/2930)). +### Internal changes + +- Removed `node-fetch` dependency when using the NPM package ([#2993](https://github.com/getsentry/sentry-cli/pull/2993)) ## 2.58.2 diff --git a/package-lock.json b/package-lock.json index 239cd6979d..6e92392e97 100644 --- a/package-lock.json +++ b/package-lock.json @@ -754,6 +754,18 @@ "rimraf": "^3.0.2", "semver": "^7.3.5", "tar": "^6.1.11" + }, + "dependencies": { + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + } } }, "@rollup/pluginutils": { @@ -1020,6 +1032,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, "requires": { "debug": "4" } @@ -1554,6 +1567,7 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, "requires": { "ms": "^2.1.3" } @@ -2455,15 +2469,6 @@ "debug": "4" } }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "requires": { - "agent-base": "6", - "debug": "4" - } - }, "human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -3429,6 +3434,18 @@ "whatwg-url": "^8.5.0", "ws": "^7.4.6", "xml-name-validator": "^3.0.0" + }, + "dependencies": { + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + } } }, "jsesc": { @@ -3714,7 +3731,8 @@ "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true }, "natural-compare": { "version": "1.4.0", @@ -3732,6 +3750,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, "requires": { "whatwg-url": "^5.0.0" }, @@ -3739,17 +3758,20 @@ "tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true }, "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true }, "whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, "requires": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -5022,6 +5044,11 @@ "which-boxed-primitive": "^1.1.1" } }, + "undici": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.16.0.tgz", + "integrity": "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==" + }, "undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", diff --git a/package.json b/package.json index ba9fbe7b5a..ef3ec240da 100644 --- a/package.json +++ b/package.json @@ -15,10 +15,9 @@ "sentry-cli": "bin/sentry-cli" }, "dependencies": { - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.7", "progress": "^2.0.3", "proxy-from-env": "^1.1.0", + "undici": "^7.16.0", "which": "^2.0.2" }, "devDependencies": { diff --git a/scripts/install.js b/scripts/install.js index 8f79ab24fc..edcce38b76 100755 --- a/scripts/install.js +++ b/scripts/install.js @@ -6,12 +6,10 @@ const fs = require('fs'); const os = require('os'); const path = require('path'); const crypto = require('crypto'); -const zlib = require('zlib'); const stream = require('stream'); const process = require('process'); -const fetch = require('node-fetch'); -const HttpsProxyAgent = require('https-proxy-agent'); +const { ProxyAgent, fetch } = require('undici'); const ProgressBar = require('progress'); const Proxy = require('proxy-from-env'); const which = require('which'); @@ -220,7 +218,7 @@ async function downloadBinary() { } const proxyUrl = Proxy.getProxyForUrl(downloadUrl); - const agent = proxyUrl ? new HttpsProxyAgent(proxyUrl) : null; + const dispatcher = proxyUrl ? new ProxyAgent(proxyUrl) : undefined; logger.log(`Downloading from ${downloadUrl}`); @@ -231,12 +229,8 @@ async function downloadBinary() { let response; try { response = await fetch(downloadUrl, { - agent, - compress: false, - headers: { - 'accept-encoding': 'gzip, deflate, br', - }, redirect: 'follow', + dispatcher, }); } catch (error) { let errorMsg = `Unable to download sentry-cli binary from ${downloadUrl}.\nError message: ${error.message}`; @@ -254,39 +248,38 @@ async function downloadBinary() { throw new Error(errorMsg); } - const contentEncoding = response.headers.get('content-encoding'); - let decompressor; - if (/\bgzip\b/.test(contentEncoding)) { - decompressor = zlib.createGunzip(); - } else if (/\bdeflate\b/.test(contentEncoding)) { - decompressor = zlib.createInflate(); - } else if (/\bbr\b/.test(contentEncoding)) { - decompressor = zlib.createBrotliDecompress(); - } else { - decompressor = new stream.PassThrough(); - } const name = downloadUrl.match(/.*\/(.*?)$/)[1]; let downloadedBytes = 0; - const totalBytes = parseInt(response.headers.get('content-length'), 10); + + // Note: content-length might not be available if response was compressed, + // as native fetch decompresses transparently + const contentLength = response.headers.get('content-length'); + const totalBytes = contentLength ? parseInt(contentLength, 10) : 0; const progressBar = createProgressBar(name, totalBytes); const tempPath = getTempFile(cachedPath); fs.mkdirSync(path.dirname(tempPath), { recursive: true }); await new Promise((resolve, reject) => { - response.body + // Convert Web ReadableStream to Node.js stream + const nodeStream = stream.Readable.fromWeb(response.body); + + nodeStream .on('error', (e) => reject(e)) .on('data', (chunk) => { downloadedBytes += chunk.length; - progressBar.tick(chunk.length); + + if (!progressBar.complete) { + progressBar.tick(chunk.length); + } }) - .pipe(decompressor) .pipe(fs.createWriteStream(tempPath, { mode: '0755' })) .on('error', (e) => reject(e)) - .on('close', () => { - if (downloadedBytes >= totalBytes) { - resolve(); - } else { + .on('finish', () => { + // Check if we have a total size to validate against + if (totalBytes > 0 && downloadedBytes < totalBytes) { reject(new Error('connection interrupted')); + } else { + resolve(); } }); });