Skip to content

Commit ea82130

Browse files
committed
refactor: fetch JSON with native fetch instead of ofetch
1 parent 669bc5e commit ea82130

16 files changed

Lines changed: 96 additions & 51 deletions

File tree

‎packages/nuxt-cli/package.json‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@
6363
"magicast": "^0.5.3",
6464
"nypm": "^0.6.8",
6565
"obug": "^2.1.1",
66-
"ofetch": "^1.5.1",
6766
"ohash": "^2.0.11",
6867
"pathe": "^2.0.3",
6968
"perfect-debounce": "^2.1.0",

‎packages/nuxt-cli/src/commands/init.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import { box, cancel, confirm, intro, isCancel, outro, S_BAR, select, spinner, t
1313
import{defineCommand,showUsage}from'citty'
1414
import{downloadTemplate,startShell}from'giget'
1515
import{detectPackageManager}from'nypm'
16-
import{$fetch}from'ofetch'
1716
import{basename,join,relative,resolve}from'pathe'
1817
importcolorsfrom'picocolors'
1918
import{findFile,readPackageJSON,writePackageJSON}from'pkg-types'
@@ -22,6 +21,7 @@ import { x } from 'tinyexec'
2221

2322
import{runCommandDefasrunCommand}from'../run-command'
2423
import{nuxtIcon,themeColor}from'../utils/ascii'
24+
import{fetchJson}from'../utils/fetch'
2525
import{createInstallLog,resolvePackageManagerDescriptor,runInstall,takeUnreportedIgnoredBuilds}from'../utils/install'
2626
import{debug,logger}from'../utils/logger'
2727
import{classifyNetworkError,describeNetworkError,logNetworkError,probeNetworkError}from'../utils/network'
@@ -429,7 +429,7 @@ export default defineCommand({
429429
constnightlySpinner=spinner()
430430
nightlySpinner.start('Fetching nightly version info')
431431

432-
constresponse=await$fetch<{'dist-tags': Record<string,string>}>(NIGHTLY_DIST_TAGS_URL).catch((err)=>{
432+
constresponse=awaitfetchJson<{'dist-tags': Record<string,string>}>(NIGHTLY_DIST_TAGS_URL).catch((err)=>{
433433
nightlySpinner.error('Failed to fetch nightly version info')
434434
logNetworkError(err,{url: NIGHTLY_DIST_TAGS_URL})
435435
process.exit(1)
@@ -772,7 +772,7 @@ export default defineCommand({
772772
asyncfunctiongetModuleDependencies(moduleName: string){
773773
consturl=`https://registry.npmjs.org/${moduleName}/latest`
774774
try{
775-
constresponse=await$fetch(url)
775+
constresponse=awaitfetchJson<{dependencies?: Record<string,string>}>(url)
776776
constdependencies=response.dependencies||{}
777777
returnObject.keys(dependencies)
778778
}

‎packages/nuxt-cli/src/commands/module/_utils.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import type { PackageJson } from 'pkg-types'
44
import{existsSync}from'node:fs'
55

66
import{confirm,isCancel}from'@clack/prompts'
7-
import{$fetch}from'ofetch'
87
import{resolve}from'pathe'
98
importcolorsfrom'picocolors'
109
import{satisfies}from'verkit'
1110

11+
import{fetchJson}from'../../utils/fetch'
1212
import{logger}from'../../utils/logger'
1313
import{relativeToProcess}from'../../utils/paths'
1414
import{cwdArgs,logLevelArgs}from'../_shared'
@@ -109,7 +109,7 @@ export interface NuxtModule {
109109
exportconstMODULES_API_URL='https://api.nuxt.com/modules?version=all'
110110

111111
exportasyncfunctionfetchModules(): Promise<NuxtModule[]>{
112-
const{ modules }=await$fetch<NuxtApiModulesResponse>(MODULES_API_URL)
112+
const{ modules }=awaitfetchJson<NuxtApiModulesResponse>(MODULES_API_URL)
113113
returnmodules
114114
}
115115

‎packages/nuxt-cli/src/commands/module/add.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import process from 'node:process'
88
import{cancel,confirm,isCancel,select,spinner}from'@clack/prompts'
99
import{defineCommand}from'citty'
1010
import{detectPackageManager,packageManagers}from'nypm'
11-
import{$fetch}from'ofetch'
1211
import{resolve}from'pathe'
1312
importcolorsfrom'picocolors'
1413
import{readPackageJSON}from'pkg-types'
@@ -17,6 +16,7 @@ import { satisfies } from 'verkit'
1716

1817
import{runCommandDefasrunCommand}from'../../run-command'
1918
import{updateConfig}from'../../utils/config'
19+
import{fetchJson}from'../../utils/fetch'
2020
import{createInstallLog,resolvePackageManagerDescriptor,runInstall,takeUnreportedIgnoredBuilds}from'../../utils/install'
2121
import{logger}from'../../utils/logger'
2222
import{logNetworkError}from'../../utils/network'
@@ -447,7 +447,7 @@ async function resolveModule(moduleName: string, cwd: string): Promise<ModuleRes
447447

448448
// TODO: spinner
449449
constpkgUrl=joinURL(meta.registry,`${pkgName}`)
450-
constpkgDetails=await$fetch(pkgUrl,{ headers }).catch((err: unknown)=>{
450+
constpkgDetails=awaitfetchJson<any>(pkgUrl,{ headers }).catch((err: unknown)=>{
451451
logNetworkError(err,{url: pkgUrl,prefix: `Failed to fetch package details for ${colors.cyan(pkgName)}.`})
452452
returnnull
453453
})
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/** Status codes worth a second attempt: transient overload, throttling and gateway faults. */
2+
constRETRY_STATUS_CODES=newSet([408,409,425,429,500,502,503,504])
3+
4+
exportinterfaceFetchJsonOptions{
5+
headers?: Record<string,string>
6+
/** Milliseconds before the request is aborted. Unlimited when omitted. */
7+
timeout?: number
8+
/** Extra attempts after the first. Defaults to one. */
9+
retry?: number
10+
}
11+
12+
interfaceFetchJsonErrorextendsError{
13+
status: number
14+
response: Response
15+
}
16+
17+
/**
18+
* `GET` a JSON document, throwing on a non-2xx response.
19+
*
20+
* Errors carry `status` and `response` so `classifyNetworkError` can tell an HTTP
21+
* failure from a transport failure. Transport errors and the status codes above
22+
* are retried, since `fetch` itself will not.
23+
*
24+
* This module deliberately has no imports: it is loaded directly by
25+
* `scripts/generate-data.ts` under Node's type stripping, where extensionless
26+
* relative specifiers do not resolve.
27+
*/
28+
exportasyncfunctionfetchJson<T>(url: string,options: FetchJsonOptions={}): Promise<T>{
29+
constattempts=(options.retry??1)+1
30+
letlastError: unknown
31+
for(letattempt=0;attempt<attempts;attempt++){
32+
try{
33+
constresponse=awaitfetch(url,{
34+
headers: options.headers,
35+
signal: options.timeout ? AbortSignal.timeout(options.timeout) : undefined,
36+
})
37+
if(response.ok){
38+
returnawaitresponse.json()asT
39+
}
40+
lastError=Object.assign(
41+
newError(`Request to ${url} failed with ${response.status}${response.statusText}`),
42+
{status: response.status, response },
43+
)satisfiesFetchJsonError
44+
if(!RETRY_STATUS_CODES.has(response.status)){
45+
break
46+
}
47+
}
48+
catch(error){
49+
lastError=error
50+
// A timeout is a deliberate deadline, so retrying would silently double it.
51+
constname=(errorasError|undefined)?.name
52+
if(name==='TimeoutError'||name==='AbortError'){
53+
break
54+
}
55+
}
56+
}
57+
throwlastError
58+
}

‎packages/nuxt-cli/src/utils/starter-templates.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
importprocessfrom'node:process'
2-
import{$fetch}from'ofetch'
2+
3+
import{fetchJson}from'./fetch.ts'
34

45
exportconsthiddenTemplates=[
56
'doc-driven',
@@ -26,7 +27,6 @@ const fetchOptions = {
2627
// than 3s. The template list is prefetched and has a static fallback, so a
2728
// slightly longer deadline only ever delays the prompt on a broken network.
2829
timeout: 5000,
29-
responseType: 'json',
3030
headers: {
3131
'user-agent': '@nuxt/cli',
3232
...process.env.GITHUB_TOKEN ? {authorization: `token ${process.env.GITHUB_TOKEN}`} : {},
@@ -45,7 +45,7 @@ export async function getTemplates() {
4545
exportasyncfunctionfetchTemplates(){
4646
consttemplates={}asRecord<string,TemplateData>
4747

48-
constfiles=await$fetch<Array<{name: string,type: string,download_url?: string}>>(
48+
constfiles=awaitfetchJson<Array<{name: string,type: string,download_url?: string}>>(
4949
TEMPLATES_API_URL,
5050
fetchOptions,
5151
)
@@ -59,7 +59,7 @@ export async function fetchTemplates() {
5959
return
6060
}
6161
templates[templateName]=undefinedasunknownasTemplateData
62-
templates[templateName]=await$fetch(file.download_url,fetchOptions)
62+
templates[templateName]=awaitfetchJson<TemplateData>(file.download_url,fetchOptions)
6363
}))
6464

6565
returntemplates

‎packages/nuxt-cli/src/utils/update.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
importprocessfrom'node:process'
22

33
import{box}from'@clack/prompts'
4-
import{$fetch}from'ofetch'
54
importcolorsfrom'picocolors'
65
import{readUser,updateUser}from'rc9'
76
import{isCI,isTest,provider}from'std-env'
87
import{joinURL}from'ufo'
98
import{isGreater,tryParse}from'verkit'
109

10+
import{fetchJson}from'./fetch'
1111
import{debug}from'./logger'
1212
import{detectNpmRegistry}from'./registry'
1313

@@ -78,7 +78,7 @@ async function resolveLatestVersion(): Promise<string | undefined> {
7878
letlatest: string|undefined
7979
try{
8080
const{ registry, authToken }=awaitdetectNpmRegistry(null)
81-
latest=(await$fetch<{latest?: string}>(joinURL(registry,'-/package/nuxt/dist-tags'),{
81+
latest=(awaitfetchJson<{latest?: string}>(joinURL(registry,'-/package/nuxt/dist-tags'),{
8282
headers: authToken ? {Authorization: `Bearer ${authToken}`} : undefined,
8383
timeout: FETCH_TIMEOUT,
8484
retry: 0,

‎packages/nuxt-cli/src/utils/versions.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import{readFileSync}from'node:fs'
22
import{resolveModulePath}from'exsolve'
3-
import{$fetch}from'ofetch'
43
import{readPackageJSON}from'pkg-types'
54
import{joinURL}from'ufo'
65
import{coerce,findMaxSatisfying}from'verkit'
76

87
import{resolveCatalogEntry}from'./catalog'
8+
import{fetchJson}from'./fetch'
99
import{tryResolveNuxt}from'./kit'
1010
import{debug}from'./logger'
1111
import{detectNpmRegistry}from'./registry'
@@ -45,7 +45,7 @@ export async function resolveRegistryVersion(pkg: string, range: string): Promis
4545
constscope=pkg.startsWith('@') ? pkg.split('/')[0]! : null
4646
const{ registry, authToken }=awaitdetectNpmRegistry(scope)
4747

48-
packument=await$fetch(joinURL(registry,pkg),{
48+
packument=awaitfetchJson(joinURL(registry,pkg),{
4949
headers: {
5050
// The abbreviated packument is a fraction of the size of the full one and
5151
// still carries every version and dist-tag.

‎packages/nuxt-cli/test/unit/commands/add.spec.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ vi.mock('pkg-types', async () => {
4040
}
4141
})
4242

43-
vi.mock('ofetch',async()=>{
43+
vi.mock('../../../src/utils/fetch',async()=>{
4444
return{
45-
$fetch: mock$fetch,
45+
fetchJson: mock$fetch,
4646
}
4747
})
4848

‎packages/nuxt-cli/test/unit/commands/module/add-config.spec.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ vi.mock('nypm', () => ({
2222
vi.mock('pkg-types',()=>({
2323
readPackageJSON: ()=>Promise.resolve({devDependencies: {nuxt: '3.0.0'}}),
2424
}))
25-
vi.mock('ofetch',()=>({
26-
$fetch: vi.fn(()=>Promise.resolve({
25+
vi.mock('../../../../src/utils/fetch',()=>({
26+
fetchJson: vi.fn(()=>Promise.resolve({
2727
'dist-tags': {latest: '1.0.0'},
2828
'versions': {'1.0.0': manifest},
2929
})),

0 commit comments

Comments
 (0)