From ca78f166697fc817b3d2e850ad1a75e787db126b Mon Sep 17 00:00:00 2001 From: Jack Herrington Date: Wed, 2 Sep 2026 08:58:58 -0700 Subject: [PATCH] fix: stop shipping broken source maps and drop direct node-fetch dep Trims the published package and removes node-fetch from `src/`. Source maps were 534 of the package's 1128 files, and none of them could ever work: they reference `../src/*.ts`, `src` is not in `files`, and none carry `sourcesContent`, so every map in an installed copy pointed at a file that wasn't there. We keep emitting them for local development and strip them at pack time instead. tarball 480 KB -> 300 KB (-38%) unpacked 2.06 MB -> 1.14 MB (-45%) files 1128 -> 595 `src` no longer imports node-fetch, so it moves to devDependencies (the test suite still uses it). Node's global fetch has been available since well before the `>=22.13.0` engine floor. Note this does not shrink an end user's install yet: @netlify/api still depends on node-fetch, and @mapbox/node-pre-gyp pulls node-fetch@2 via @vercel/nft. Two bugs surfaced while doing this: - functions-create downloaded template files with `res.body?.pipe(dest)`, which was never awaited, so `Promise.all` resolved before the writes finished. Global fetch returns a web ReadableStream with no `.pipe()` at all, so this is now `await pipeline(...)`, which also fixes the race. - `npm run clean` deleted `dist/` but left `tsconfig.build.tsbuildinfo`, so the next build saw an up-to-date cache and emitted nothing at all. Both the clean script and the pack-time strip now clear it. Co-Authored-By: Claude Opus 5 (1M context) --- package-lock.json | 253 ++++-------------- package.json | 5 +- scripts/strip-source-maps.js | 92 +++++++ src/commands/create/create-action.ts | 1 - src/commands/functions/functions-create.ts | 7 +- src/commands/functions/functions-invoke.ts | 1 - src/lib/geo-location.ts | 1 - src/utils/deploy/upload-source-zip.ts | 1 - src/utils/read-repo-url.ts | 2 - src/utils/telemetry/request.ts | 2 - .../utils/deploy/upload-source-zip.test.ts | 30 +-- 11 files changed, 166 insertions(+), 229 deletions(-) create mode 100644 scripts/strip-source-maps.js diff --git a/package-lock.json b/package-lock.json index ab6811c6525..f21b4f14cf1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -82,7 +82,6 @@ "multiparty": "^4.2.3", "nanospinner": "^1.2.2", "netlify-redirector": "^0.5.0", - "node-fetch": "^3.3.2", "normalize-package-data": "^7.0.1", "open": "^11.0.0", "p-filter": "^4.1.0", @@ -161,6 +160,7 @@ "lodash.shuffle": "^4.2.0", "memfs": "^4.56.10", "nock": "^14.0.10", + "node-fetch": "^3.3.2", "npm-run-all2": "^8.0.4", "oxfmt": "0.61.0", "p-timeout": "^7.0.0", @@ -5841,10 +5841,6 @@ "node": ">= 20" } }, - "node_modules/@octokit/openapi-types": { - "version": "26.0.0", - "license": "MIT" - }, "node_modules/@octokit/plugin-paginate-rest": { "version": "13.2.0", "license": "MIT", @@ -5925,6 +5921,12 @@ "@octokit/openapi-types": "^26.0.0" } }, + "node_modules/@octokit/types/node_modules/@octokit/openapi-types": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-26.0.0.tgz", + "integrity": "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA==", + "license": "MIT" + }, "node_modules/@open-draft/deferred-promise": { "version": "2.2.0", "dev": true, @@ -6581,7 +6583,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6595,7 +6596,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6609,7 +6609,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6623,7 +6622,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6637,7 +6635,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6651,7 +6648,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6665,7 +6661,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6679,7 +6674,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6693,7 +6687,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6707,7 +6700,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6721,7 +6713,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6735,7 +6726,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6749,7 +6739,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6763,7 +6752,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6777,7 +6765,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6791,7 +6778,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6805,7 +6791,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6819,7 +6804,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6833,7 +6817,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6847,7 +6830,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6861,7 +6843,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6875,7 +6856,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6889,7 +6869,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6903,7 +6882,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6917,7 +6895,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6942,7 +6919,9 @@ } }, "node_modules/@sindresorhus/slugify": { - "version": "3.0.0", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-3.0.1.tgz", + "integrity": "sha512-N1oPvN6sytRKZCskv0L+vSyvnUYi9JplPXZ90lT6EBsijjHrUdoXttb0Lna6pw1TWMe8vlajQnLDtU9KYHqtDg==", "dev": true, "license": "MIT", "dependencies": { @@ -7493,9 +7472,9 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", - "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.8.tgz", + "integrity": "sha512-YYNsSlXBjMk92SKnkwvB5LOVSa6OznlFUGcsvrFgNJbJCd0M1XKeFVRc8ZByeCqz32FivYNHJVooLmdqrmvp/Q==", "dev": true, "license": "MIT", "engines": { @@ -11771,9 +11750,9 @@ } }, "node_modules/dot-prop/node_modules/type-fest": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.8.0.tgz", - "integrity": "sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.9.0.tgz", + "integrity": "sha512-yANm3Jr3GiJ1qgJlxGAVxTOIcEOk1rhQHamlXtnrCK7EHP4HeM9OGxtMg/W7HFdrVzw/ZWJKGVIJusVH85sLtw==", "license": "(MIT OR CC0-1.0)", "dependencies": { "tagged-tag": "^1.0.0" @@ -12949,10 +12928,32 @@ } }, "node_modules/express/node_modules/negotiator": { - "version": "1.0.0", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", "license": "MIT", + "dependencies": { + "content-type": "^2.1.0" + }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/negotiator/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/send": { @@ -13642,7 +13643,6 @@ }, "node_modules/fsevents": { "version": "2.3.3", - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14033,9 +14033,9 @@ } }, "node_modules/got/node_modules/type-fest": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.8.0.tgz", - "integrity": "sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.9.0.tgz", + "integrity": "sha512-yANm3Jr3GiJ1qgJlxGAVxTOIcEOk1rhQHamlXtnrCK7EHP4HeM9OGxtMg/W7HFdrVzw/ZWJKGVIJusVH85sLtw==", "dev": true, "license": "(MIT OR CC0-1.0)", "dependencies": { @@ -14837,134 +14837,6 @@ "ipx": "bin/ipx.mjs" } }, - "node_modules/ipx/node_modules/@netlify/blobs": { - "version": "10.7.13", - "resolved": "https://registry.npmjs.org/@netlify/blobs/-/blobs-10.7.13.tgz", - "integrity": "sha512-LJnmGtQQ2/NdTo0Cm+YP2xR1vtRle6V3kkzMrAOJgRm1SEFOJOcaAFQrth7RnbxCH/IzCM8QakTaCHHKY+K2qA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@netlify/dev-utils": "5.0.0", - "@netlify/otel": "^6.0.6", - "@netlify/runtime-utils": "2.3.0" - }, - "engines": { - "node": "^14.16.0 || >=16.0.0" - } - }, - "node_modules/ipx/node_modules/@netlify/dev-utils": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@netlify/dev-utils/-/dev-utils-5.0.0.tgz", - "integrity": "sha512-ICAsnvbJW9Dv9PGfmJGdjMBsX6uXdJxrA76QE3l3rnGKL5ZcyaA2cJZXacdEmE2ZmtkiVhEJogM15a9nK/ZxDw==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@whatwg-node/server": "^0.11.0", - "ansis": "^4.1.0", - "atomically": "^2.0.3", - "chokidar": "^4.0.1", - "decache": "^4.6.2", - "dettle": "^1.0.5", - "dot-prop": "9.0.0", - "empathic": "^2.0.0", - "env-paths": "^3.0.0", - "parse-gitignore": "^2.0.0", - "semver": "^7.7.2" - }, - "engines": { - "node": "^18.14.0 || >=20" - } - }, - "node_modules/ipx/node_modules/@netlify/dev-utils/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/ipx/node_modules/@netlify/otel": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/@netlify/otel/-/otel-6.0.6.tgz", - "integrity": "sha512-KpiJ8c4V4GvgpQH5E1axg43kYdIN9saToLbzvgrDzPun4XMGwz/tLfoIkwJ4jwrVmbPj35+8+dj3gkggQAtjtg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@opentelemetry/api": "1.9.1", - "@opentelemetry/core": "2.8.0", - "@opentelemetry/instrumentation": "^0.220.0", - "@opentelemetry/resources": "2.9.0", - "@opentelemetry/sdk-trace-node": "2.9.0" - }, - "engines": { - "node": "^18.14.0 || >=20.6.1" - } - }, - "node_modules/ipx/node_modules/@netlify/runtime-utils": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@netlify/runtime-utils/-/runtime-utils-2.3.0.tgz", - "integrity": "sha512-cW8weDvsKV7zfia2m5EcBy6KILGoPD+eYZ3qWNGnIo05DGF28goPES0xKSDkNYgAF/2rRSIhie2qcBhbGVgSRg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": "^18.14.0 || >=20" - } - }, - "node_modules/ipx/node_modules/@opentelemetry/api": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", - "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/ipx/node_modules/dot-prop": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz", - "integrity": "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "type-fest": "^4.18.2" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ipx/node_modules/env-paths": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", - "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ipx/node_modules/lru-cache": { "version": "11.5.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", @@ -14974,21 +14846,6 @@ "node": "20 || >=22" } }, - "node_modules/ipx/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/ipx/node_modules/unstorage": { "version": "1.17.5", "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.5.tgz", @@ -15814,9 +15671,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", - "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, "funding": [ { @@ -16244,9 +16101,9 @@ "license": "MIT" }, "node_modules/listhen/node_modules/crossws": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.4.10.tgz", - "integrity": "sha512-pz3oubH/dt12KjqsUB0IuXW4nwRDQ583iDsP4555Cpdqx0NoU7pGlWBcayyFI8f/l/idRpgjMEfwuOxSWJYlIA==", + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.4.12.tgz", + "integrity": "sha512-aypfsr6t0uNvkqaZc6zvBfXzC6pLI0/sIulpkV6RwCVtZqG5ebBzv4weImKK0VNCj91Wl9F5j7p5WU4MNrybng==", "license": "MIT", "peerDependencies": { "srvx": ">=0.11.5" @@ -18226,9 +18083,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "license": "MIT", "engines": { "node": ">=12" @@ -18865,9 +18722,9 @@ } }, "node_modules/read-package-up/node_modules/type-fest": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.8.0.tgz", - "integrity": "sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.9.0.tgz", + "integrity": "sha512-yANm3Jr3GiJ1qgJlxGAVxTOIcEOk1rhQHamlXtnrCK7EHP4HeM9OGxtMg/W7HFdrVzw/ZWJKGVIJusVH85sLtw==", "license": "(MIT OR CC0-1.0)", "dependencies": { "tagged-tag": "^1.0.0" diff --git a/package.json b/package.json index 27a1216584e..94ed0f05e39 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "type": "module", "scripts": { "build": "node ./node_modules/typescript-native/bin/tsc --project tsconfig.build.json", - "clean": "rm -rf dist/", + "clean": "rm -rf dist/ tsconfig.build.tsbuildinfo tsconfig.tsbuildinfo", "dev": "node ./node_modules/typescript-native/bin/tsc --project tsconfig.build.json --watch", "docs": "npm run --prefix=site build", "format": "oxfmt --write", @@ -47,6 +47,7 @@ "test:integration": "vitest run --retry=3 tests/integration/", "test:unit": "vitest run tests/unit/", "postinstall": "node ./scripts/postinstall.js", + "prepack": "node ./scripts/strip-source-maps.js", "typecheck": "node ./node_modules/typescript-native/bin/tsc", "typecheck:watch": "node ./node_modules/typescript-native/bin/tsc --watch" }, @@ -123,7 +124,6 @@ "multiparty": "^4.2.3", "nanospinner": "^1.2.2", "netlify-redirector": "^0.5.0", - "node-fetch": "^3.3.2", "normalize-package-data": "^7.0.1", "open": "^11.0.0", "p-filter": "^4.1.0", @@ -198,6 +198,7 @@ "lodash.shuffle": "^4.2.0", "memfs": "^4.56.10", "nock": "^14.0.10", + "node-fetch": "^3.3.2", "npm-run-all2": "^8.0.4", "oxfmt": "0.61.0", "p-timeout": "^7.0.0", diff --git a/scripts/strip-source-maps.js b/scripts/strip-source-maps.js new file mode 100644 index 00000000000..478c48d97f6 --- /dev/null +++ b/scripts/strip-source-maps.js @@ -0,0 +1,92 @@ +/* + * This script runs at pack time (`prepack`), before the tarball is assembled. + * + * We build with `sourceMap` and `declarationMap` enabled so that local development against `dist/` + * has working maps. Those maps are useless to end users, though: they reference `../src/*.ts`, and + * `src` is not in the package's `files` list, so every map in a published install points at a file + * that isn't there. They accounted for roughly half of the published package, so we strip them + * here rather than shipping dead weight. + * + * This is idempotent — `npm publish` is invoked more than once per release (see + * `.github/workflows/release-please.yml`), and the second run should be a no-op. + */ + +import { readdir, rm, readFile, stat, writeFile } from 'node:fs/promises' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const DIST_DIR = path.join(__dirname, '..', 'dist') +// `tsc --incremental` decides what to emit from this file alone, not from what's on disk. Stripping +// maps out from under it would otherwise leave a subsequent local build convinced it has nothing to +// do, silently yielding a `dist/` with no maps (or, after `npm run clean`, no `dist/` at all). +const BUILD_INFO_FILE = path.join(__dirname, '..', 'tsconfig.build.tsbuildinfo') + +// Matches the trailing `//# sourceMappingURL=...` annotation left behind once the map is gone. +const SOURCE_MAPPING_URL_RE = /^\/\/# sourceMappingURL=.*$\n?/gm + +const walk = async (dir) => { + let entries + try { + entries = await readdir(dir, { withFileTypes: true }) + } catch (error) { + if (error.code === 'ENOENT') { + return [] + } + throw error + } + + const files = await Promise.all( + entries.map(async (entry) => { + const entryPath = path.join(dir, entry.name) + return entry.isDirectory() ? walk(entryPath) : [entryPath] + }), + ) + return files.flat() +} + +const stripSourceMaps = async () => { + const files = await walk(DIST_DIR) + + if (files.length === 0) { + console.error('strip-source-maps: no dist/ output found, nothing to do') + return + } + + const maps = files.filter((file) => file.endsWith('.map')) + const annotated = files.filter((file) => file.endsWith('.js') || file.endsWith('.d.ts')) + + // Collect sizes first and sum at the end: `total += await size(file)` would read `total` before + // awaiting and clobber every concurrent update. + const bytesRemoved = ( + await Promise.all( + maps.map(async (file) => { + const { size } = await stat(file) + await rm(file) + return size + }), + ) + ).reduce((total, size) => total + size, 0) + + let annotationsRemoved = 0 + await Promise.all( + annotated.map(async (file) => { + const contents = await readFile(file, 'utf8') + const stripped = contents.replace(SOURCE_MAPPING_URL_RE, '') + if (stripped !== contents) { + annotationsRemoved += 1 + await writeFile(file, stripped) + } + }), + ) + + await rm(BUILD_INFO_FILE, { force: true }) + + console.error( + `strip-source-maps: removed ${maps.length.toString()} map file(s) (${(bytesRemoved / 1024 / 1024).toFixed( + 2, + )} MB) and ${annotationsRemoved.toString()} sourceMappingURL annotation(s)`, + ) +} + +await stripSourceMaps() diff --git a/src/commands/create/create-action.ts b/src/commands/create/create-action.ts index 7e76284674e..42aafa891e0 100644 --- a/src/commands/create/create-action.ts +++ b/src/commands/create/create-action.ts @@ -9,7 +9,6 @@ import { promisify } from 'util' import type { OptionValues } from 'commander' import inquirer from 'inquirer' -import fetch from 'node-fetch' import type { NetlifyAPI } from '@netlify/api' import { LocalState } from '@netlify/dev-utils' diff --git a/src/commands/functions/functions-create.ts b/src/commands/functions/functions-create.ts index 093f829c56b..1bd62c3ca78 100644 --- a/src/commands/functions/functions-create.ts +++ b/src/commands/functions/functions-create.ts @@ -4,13 +4,13 @@ import { mkdir, readdir, unlink } from 'fs/promises' import { createRequire } from 'module' import path, { dirname, join, relative } from 'path' import process from 'process' +import { pipeline } from 'stream/promises' import { fileURLToPath, pathToFileURL } from 'url' import { OptionValues } from 'commander' import { findUp } from 'find-up' import fuzzy from 'fuzzy' import inquirer from 'inquirer' -import fetch from 'node-fetch' import { createSpinner } from 'nanospinner' import { fileExistsAsync } from '../../lib/fs.js' @@ -409,10 +409,13 @@ const downloadFromURL = async function (command, options, argumentName, function folderContents.map(async ({ download_url: downloadUrl, name }) => { try { const res = await fetch(downloadUrl) + if (!res.ok || !res.body) { + throw new Error(`HTTP ${res.status.toString()}: ${res.statusText}`) + } const fileName = path.basename(name) const finalName = path.basename(fileName, '.js') === functionName ? `${nameToUse}.js` : fileName const dest = fs.createWriteStream(path.join(fnFolder, finalName)) - res.body?.pipe(dest) + await pipeline(res.body, dest) } catch (error_) { throw new Error(`Error while retrieving ${downloadUrl} ${error_}`) } diff --git a/src/commands/functions/functions-invoke.ts b/src/commands/functions/functions-invoke.ts index 04a4788a9b4..77ab52f54e7 100644 --- a/src/commands/functions/functions-invoke.ts +++ b/src/commands/functions/functions-invoke.ts @@ -4,7 +4,6 @@ import path from 'path' import { OptionValues } from 'commander' import inquirer from 'inquirer' -import fetch from 'node-fetch' import { APIError, NETLIFYDEVWARN, chalk, logAndThrowError, exit } from '../../utils/command-helpers.js' import { BACKGROUND, CLOCKWORK_USERAGENT, getFunctions } from '../../utils/functions/index.js' diff --git a/src/lib/geo-location.ts b/src/lib/geo-location.ts index 875925ec544..e7d7b493ccf 100644 --- a/src/lib/geo-location.ts +++ b/src/lib/geo-location.ts @@ -1,4 +1,3 @@ -import fetch from 'node-fetch' import { type Geolocation, mockLocation } from '@netlify/dev-utils' const API_URL = 'https://netlifind.netlify.app' diff --git a/src/utils/deploy/upload-source-zip.ts b/src/utils/deploy/upload-source-zip.ts index 2865f436a07..5e84373a857 100644 --- a/src/utils/deploy/upload-source-zip.ts +++ b/src/utils/deploy/upload-source-zip.ts @@ -4,7 +4,6 @@ import type { PathLike } from 'node:fs' import { platform } from 'node:os' import execa, { ExecaError } from 'execa' -import fetch from 'node-fetch' import { log, warn } from '../command-helpers.js' import { temporaryDirectory } from '../temporary-file.js' diff --git a/src/utils/read-repo-url.ts b/src/utils/read-repo-url.ts index 84fb40b7061..0793c74a110 100644 --- a/src/utils/read-repo-url.ts +++ b/src/utils/read-repo-url.ts @@ -1,7 +1,5 @@ import URL from 'url' -import fetch from 'node-fetch' - // supported repo host types const GITHUB = 'GitHub' diff --git a/src/utils/telemetry/request.ts b/src/utils/telemetry/request.ts index e3c2bfb2b83..93481134642 100644 --- a/src/utils/telemetry/request.ts +++ b/src/utils/telemetry/request.ts @@ -2,8 +2,6 @@ // to run as a detached process import process from 'process' -import fetch from 'node-fetch' - import getPackageJson from '../get-cli-package-json.js' const { name, version } = await getPackageJson() diff --git a/tests/unit/utils/deploy/upload-source-zip.test.ts b/tests/unit/utils/deploy/upload-source-zip.test.ts index 1bb93c588ee..260fb870c5e 100644 --- a/tests/unit/utils/deploy/upload-source-zip.test.ts +++ b/tests/unit/utils/deploy/upload-source-zip.test.ts @@ -1,13 +1,11 @@ import { join } from 'node:path' import type { ExecaReturnValue } from 'execa' -import type { Response } from 'node-fetch' import { describe, expect, test, vi, beforeEach } from 'vitest' // Mock all dependencies at the top level -vi.mock('node-fetch', () => ({ - default: vi.fn(), -})) +const mockFetch = vi.fn() +vi.stubGlobal('fetch', mockFetch) vi.mock('execa', () => ({ default: vi.fn(), @@ -47,13 +45,12 @@ describe('uploadSourceZip', () => { const { uploadSourceZip } = await import('../../../../src/utils/deploy/upload-source-zip.js') // Setup mocks using vi.mocked() - const mockFetch = await import('node-fetch') const mockExeca = await import('execa') const mockFs = await import('fs/promises') const mockCommandHelpers = await import('../../../../src/utils/command-helpers.js') const mockTempFile = await import('../../../../src/utils/temporary-file.js') - vi.mocked(mockFetch.default).mockResolvedValue({ + mockFetch.mockResolvedValue({ ok: true, status: 200, statusText: 'OK', @@ -83,7 +80,7 @@ describe('uploadSourceZip', () => { expect.objectContaining({ cwd: '/test/source' }), ) - expect(mockFetch.default).toHaveBeenCalledWith( + expect(mockFetch).toHaveBeenCalledWith( 'https://s3.example.com/upload-url', expect.objectContaining({ method: 'PUT', @@ -110,13 +107,12 @@ describe('uploadSourceZip', () => { const { uploadSourceZip } = await import('../../../../src/utils/deploy/upload-source-zip.js') - const mockFetch = await import('node-fetch') const mockExeca = await import('execa') const mockFs = await import('fs/promises') const mockCommandHelpers = await import('../../../../src/utils/command-helpers.js') const mockTempFile = await import('../../../../src/utils/temporary-file.js') - vi.mocked(mockFetch.default).mockResolvedValue({ + mockFetch.mockResolvedValue({ ok: false, status: 403, statusText: 'Forbidden', @@ -160,13 +156,12 @@ describe('uploadSourceZip', () => { const { uploadSourceZip } = await import('../../../../src/utils/deploy/upload-source-zip.js') - const mockFetch = await import('node-fetch') const mockExeca = await import('execa') const mockFs = await import('fs/promises') const mockCommandHelpers = await import('../../../../src/utils/command-helpers.js') const mockTempFile = await import('../../../../src/utils/temporary-file.js') - vi.mocked(mockFetch.default).mockResolvedValue({ + mockFetch.mockResolvedValue({ ok: true, status: 200, statusText: 'OK', @@ -274,7 +269,6 @@ describe('uploadSourceZip', () => { const { uploadSourceZip } = await import('../../../../src/utils/deploy/upload-source-zip.js') - const mockFetch = await import('node-fetch') const mockExeca = await import('execa') const mockFs = await import('fs/promises') const mockCommandHelpers = await import('../../../../src/utils/command-helpers.js') @@ -287,11 +281,11 @@ describe('uploadSourceZip', () => { }) vi.mocked(mockFs.readFile).mockResolvedValue(Buffer.from('mock zip content')) - vi.mocked(mockFetch.default).mockResolvedValue({ + mockFetch.mockResolvedValue({ ok: false, status: 500, statusText: 'Internal Server Error', - } as unknown as import('node-fetch').Response) + } as unknown as Response) vi.mocked(mockCommandHelpers.warn).mockImplementation(() => {}) vi.mocked(mockTempFile.temporaryDirectory).mockReturnValue('/tmp/test-temp-dir') @@ -319,18 +313,17 @@ describe('uploadSourceZip', () => { const { uploadSourceZip } = await import('../../../../src/utils/deploy/upload-source-zip.js') - const mockFetch = await import('node-fetch') const mockExeca = await import('execa') const mockFs = await import('fs/promises') const mockCommandHelpers = await import('../../../../src/utils/command-helpers.js') const mockTempFile = await import('../../../../src/utils/temporary-file.js') - vi.mocked(mockFetch.default).mockResolvedValue({ + mockFetch.mockResolvedValue({ ok: true, status: 200, statusText: 'OK', json: vi.fn().mockResolvedValue({ url: 'https://test-source-zip-url.com' }), - } as unknown as import('node-fetch').Response) + } as unknown as Response) // @ts-expect-error(ndhoule): getting the type on this fairly challenging vi.mocked(mockExeca.default).mockImplementation((..._args) => { @@ -359,13 +352,12 @@ describe('uploadSourceZip', () => { const { uploadSourceZip } = await import('../../../../src/utils/deploy/upload-source-zip.js') - const mockFetch = await import('node-fetch') const mockExeca = await import('execa') const mockFs = await import('fs/promises') const mockCommandHelpers = await import('../../../../src/utils/command-helpers.js') const mockTempFile = await import('../../../../src/utils/temporary-file.js') - vi.mocked(mockFetch.default).mockResolvedValue({ + mockFetch.mockResolvedValue({ ok: true, status: 200, statusText: 'OK',