Compare commits

...

3 Commits

Author SHA1 Message Date
shadcn
01b72a39ba fix(shadcn): avoid Error.cause typings for older TS lib targets 2026-06-10 12:25:03 +04:00
shadcn
2be26ddbb7 fix(shadcn): surface network failure reason from fetch errors 2026-06-10 12:12:54 +04:00
shadcn
cef302ad5a chore: replace node-fetch 2026-06-10 11:45:45 +04:00
6 changed files with 95 additions and 83 deletions

View File

@@ -0,0 +1,5 @@
---
"shadcn": patch
---
replace node-fetch with native fetch

View File

@@ -61,6 +61,9 @@
}
},
"bin": "./dist/index.js",
"engines": {
"node": ">=20.18.1"
},
"scripts": {
"dev": "tsup --watch",
"build": "tsup",
@@ -98,9 +101,7 @@
"fast-glob": "^3.3.3",
"fs-extra": "^11.3.1",
"fuzzysort": "^3.1.0",
"https-proxy-agent": "^7.0.6",
"kleur": "^4.1.5",
"node-fetch": "^3.3.2",
"open": "^11.0.0",
"ora": "^8.2.0",
"postcss": "^8.5.6",
@@ -111,6 +112,7 @@
"tailwind-merge": "^3.0.1",
"ts-morph": "^26.0.0",
"tsconfig-paths": "^4.2.0",
"undici": "^7.27.2",
"validate-npm-package-name": "^7.0.1",
"zod": "^3.24.1",
"zod-to-json-schema": "^3.24.6"

View File

@@ -12,15 +12,10 @@ import {
RegistryParseError,
RegistryUnauthorizedError,
} from "@/src/registry/errors"
import { fetchWithProxy } from "@/src/registry/proxy"
import { registryItemSchema } from "@/src/schema"
import { HttpsProxyAgent } from "https-proxy-agent"
import fetch, { Headers } from "node-fetch"
import { z } from "zod"
const agent = process.env.https_proxy
? new HttpsProxyAgent(process.env.https_proxy)
: undefined
const registryCache = new Map<string, Promise<any>>()
export function clearRegistryCache() {
@@ -59,8 +54,7 @@ export async function fetchRegistry(
requestHeaders.set(key, value)
}
const response = await fetch(url, {
agent,
const response = await fetchWithProxy(url, {
headers: requestHeaders,
})

View File

@@ -5,21 +5,16 @@ import type {
import { RegistryError, RegistrySourceFileError } from "@/src/registry/errors"
import { resolveGitHubRef } from "@/src/registry/github-ref"
import type { GitHubSource } from "@/src/registry/github-ref"
import { fetchWithProxy } from "@/src/registry/proxy"
import {
loadRegistryCatalogFromSource,
loadRegistryItemFromSource,
} from "@/src/registry/source"
import type { RegistrySourceReader } from "@/src/registry/source"
import { HttpsProxyAgent } from "https-proxy-agent"
import fetch, { Headers } from "node-fetch"
const GITHUB_RAW_URL = "https://raw.githubusercontent.com"
const GITHUB_VALIDATION_CONCURRENCY = 8
const agent = process.env.https_proxy
? new HttpsProxyAgent(process.env.https_proxy)
: undefined
type GitHubItemAddress = Extract<ResolvedItemAddress, { scheme: "github" }>
type GitHubRegistryValidationDiagnostic = {
@@ -180,10 +175,9 @@ async function fetchGitHubSourceFile(
filePath: string,
address: GitHubSource
) {
let response: Awaited<ReturnType<typeof fetch>>
let response: Response
try {
response = await fetch(url, {
agent,
response = await fetchWithProxy(url, {
headers: new Headers({
"Accept-Encoding": "identity",
"User-Agent": "shadcn",

View File

@@ -0,0 +1,60 @@
import { EnvHttpProxyAgent } from "undici"
// Native fetch ignores the http.Agent-based `agent` option, so proxy support
// goes through an undici dispatcher instead. EnvHttpProxyAgent honors
// http_proxy, https_proxy and no_proxy (upper and lowercase).
const proxyDispatcher =
process.env.https_proxy ||
process.env.HTTPS_PROXY ||
process.env.http_proxy ||
process.env.HTTP_PROXY
? new EnvHttpProxyAgent()
: undefined
export async function fetchWithProxy(url: string | URL, init?: RequestInit) {
try {
// The `dispatcher` option is supported by Node's fetch at runtime but
// missing from the ambient RequestInit type, hence the cast.
return await fetch(url, {
...init,
dispatcher: proxyDispatcher,
} as RequestInit)
} catch (error) {
// Native fetch reports network failures as a generic "fetch failed"
// TypeError with the actual reason buried in `cause`. The casts are
// needed because the configured TS lib predates Error.cause.
const cause =
error instanceof TypeError
? (error as TypeError & { cause?: unknown }).cause
: undefined
if (cause) {
const enriched = new Error(
`Request to ${url} failed, reason: ${getFailureReason(cause)}`
) as Error & { cause?: unknown }
enriched.cause = cause
throw enriched
}
throw error
}
}
function getFailureReason(cause: unknown): string {
// Connection failures surface as an AggregateError with an empty message
// and the per-address errors (e.g. ECONNREFUSED) in `errors`.
if (cause instanceof Error && "errors" in cause) {
const errors = (cause as Error & { errors: unknown }).errors
if (Array.isArray(errors) && errors.length) {
return getFailureReason(errors[0])
}
}
if (cause instanceof Error) {
return (
cause.message || (cause as NodeJS.ErrnoException).code || "unknown error"
)
}
return String(cause)
}

85
pnpm-lock.yaml generated
View File

@@ -428,15 +428,9 @@ importers:
fuzzysort:
specifier: ^3.1.0
version: 3.1.0
https-proxy-agent:
specifier: ^7.0.6
version: 7.0.6
kleur:
specifier: ^4.1.5
version: 4.1.5
node-fetch:
specifier: ^3.3.2
version: 3.3.2
open:
specifier: ^11.0.0
version: 11.0.0
@@ -467,6 +461,9 @@ importers:
tsconfig-paths:
specifier: ^4.2.0
version: 4.2.0
undici:
specifier: ^7.27.2
version: 7.27.2
validate-npm-package-name:
specifier: ^7.0.1
version: 7.0.1
@@ -534,7 +531,7 @@ importers:
version: 5.9.2
vite-tsconfig-paths:
specifier: ^4.2.0
version: 4.3.2(typescript@5.9.2)(vite@5.4.19(@types/node@20.19.10)(lightningcss@1.30.2))
version: 4.3.2(typescript@5.9.2)(vite@7.3.2(@types/node@20.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.3)(yaml@2.8.1))
vitest:
specifier: ^2.1.9
version: 2.1.9(@types/node@20.19.10)(lightningcss@1.30.2)(msw@2.10.4(@types/node@20.19.10)(typescript@5.9.2))
@@ -4489,10 +4486,6 @@ packages:
resolution: {integrity: sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==}
engines: {node: '>=12'}
data-uri-to-buffer@4.0.1:
resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
engines: {node: '>= 12'}
data-uri-to-buffer@6.0.2:
resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==}
engines: {node: '>= 14'}
@@ -5145,10 +5138,6 @@ packages:
picomatch:
optional: true
fetch-blob@3.2.0:
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
engines: {node: ^12.20 || >= 14.13}
figures@6.1.0:
resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==}
engines: {node: '>=18'}
@@ -5208,10 +5197,6 @@ packages:
resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==}
engines: {node: '>= 6'}
formdata-polyfill@4.0.10:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
engines: {node: '>=12.20.0'}
forwarded@0.2.0:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'}
@@ -6545,11 +6530,6 @@ packages:
sass:
optional: true
node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'}
deprecated: Use your platform's native DOMException instead
node-fetch@2.7.0:
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
engines: {node: 4.x || >=6.0.0}
@@ -6559,10 +6539,6 @@ packages:
encoding:
optional: true
node-fetch@3.3.2:
resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
node-releases@2.0.21:
resolution: {integrity: sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==}
@@ -8002,6 +7978,10 @@ packages:
undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
undici@7.27.2:
resolution: {integrity: sha512-uZsKNuzQxDMUY6M3pIMvy5tvlGmtq8XJ2oLAkfRKGNu+1VQAIvLy2xIVG5ATZl5wDXl/tddByAWCizRbOme+TA==}
engines: {node: '>=20.18.1'}
unicorn-magic@0.1.0:
resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==}
engines: {node: '>=18'}
@@ -8295,10 +8275,6 @@ packages:
web-namespaces@2.0.1:
resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
web-streams-polyfill@3.3.3:
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
engines: {node: '>= 8'}
webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
@@ -12241,8 +12217,6 @@ snapshots:
dargs@8.1.0: {}
data-uri-to-buffer@4.0.1: {}
data-uri-to-buffer@6.0.2: {}
data-view-buffer@1.0.2:
@@ -13311,11 +13285,6 @@ snapshots:
optionalDependencies:
picomatch: 4.0.4
fetch-blob@3.2.0:
dependencies:
node-domexception: 1.0.0
web-streams-polyfill: 3.3.3
figures@6.1.0:
dependencies:
is-unicode-supported: 2.1.0
@@ -13389,10 +13358,6 @@ snapshots:
hasown: 2.0.2
mime-types: 2.1.35
formdata-polyfill@4.0.10:
dependencies:
fetch-blob: 3.2.0
forwarded@0.2.0: {}
fraction.js@4.3.7: {}
@@ -14957,18 +14922,10 @@ snapshots:
- '@babel/core'
- babel-plugin-macros
node-domexception@1.0.0: {}
node-fetch@2.7.0:
dependencies:
whatwg-url: 5.0.0
node-fetch@3.3.2:
dependencies:
data-uri-to-buffer: 4.0.1
fetch-blob: 3.2.0
formdata-polyfill: 4.0.10
node-releases@2.0.21: {}
normalize-path@3.0.0: {}
@@ -16686,6 +16643,8 @@ snapshots:
undici-types@6.21.0: {}
undici@7.27.2: {}
unicorn-magic@0.1.0: {}
unicorn-magic@0.3.0: {}
@@ -16917,17 +16876,6 @@ snapshots:
- supports-color
- terser
vite-tsconfig-paths@4.3.2(typescript@5.9.2)(vite@5.4.19(@types/node@20.19.10)(lightningcss@1.30.2)):
dependencies:
debug: 4.4.1
globrex: 0.1.2
tsconfck: 3.1.6(typescript@5.9.2)
optionalDependencies:
vite: 5.4.19(@types/node@20.19.10)(lightningcss@1.30.2)
transitivePeerDependencies:
- supports-color
- typescript
vite-tsconfig-paths@4.3.2(typescript@5.9.2)(vite@7.3.2(@types/node@20.19.10)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.20.3)(yaml@2.8.1)):
dependencies:
debug: 4.4.1
@@ -16939,6 +16887,17 @@ snapshots:
- supports-color
- typescript
vite-tsconfig-paths@4.3.2(typescript@5.9.2)(vite@7.3.2(@types/node@20.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.3)(yaml@2.8.1)):
dependencies:
debug: 4.4.1
globrex: 0.1.2
tsconfck: 3.1.6(typescript@5.9.2)
optionalDependencies:
vite: 7.3.2(@types/node@20.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.3)(yaml@2.8.1)
transitivePeerDependencies:
- supports-color
- typescript
vite@5.4.19(@types/node@20.19.10)(lightningcss@1.30.2):
dependencies:
esbuild: 0.21.5
@@ -17029,8 +16988,6 @@ snapshots:
web-namespaces@2.0.1: {}
web-streams-polyfill@3.3.3: {}
webidl-conversions@3.0.1: {}
webidl-conversions@4.0.2: {}