Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bench/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pnpm bench:cli --suite startup --startup-reps 31
## Suites

- **`startup`** - cold `nuxi --version`, `--help`, and unknown-command time. Catches anything pulled onto the module graph of the fast paths.
- **`modules`** - how many modules each target loads for those same commands, via `bench/lib/module-hook.mjs`. A count rather than a time, so it is stable across machines and is the better regression guard of the two.
- **`modules`** - how many modules (and Node.js built-ins) each target loads for those same commands, via `bench/lib/module-hook.mjs`. A count rather than a time, so it is stable across machines and is the better regression guard of the two.
- **`panel`** - time from spawn to the first painted `nuxt dev` frame, and until a keypress at that frame is answered.
- **`dev`** - time from spawn to the dev server answering a request.
- **`restart`** - time to serve again after a `nuxt.config.ts` edit, plus a no-op edit case to check that ignored changes stay ignored.
Expand Down
4 changes: 3 additions & 1 deletion bench/lib/module-hook.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import module from 'node:module'
import process from 'node:process'

const loaded = new Map()
const bootBuiltins = new Set(process.moduleLoadList)

module.registerHooks({
load(url, context, nextLoad) {
Expand All @@ -25,5 +26,6 @@ process.on('exit', () => {
}
}
// `_rawDebug` writes straight to fd 2 and cannot be intercepted by the CLI's own logging
process._rawDebug(`__BENCH_MODULES__${JSON.stringify({ modules: loaded.size, nodeModules, bytes })}`)
const builtins = process.moduleLoadList.filter(entry => entry.startsWith('NativeModule ') && !bootBuiltins.has(entry)).length
process._rawDebug(`__BENCH_MODULES__${JSON.stringify({ modules: loaded.size, nodeModules, bytes, builtins })}`)
})
5 changes: 4 additions & 1 deletion bench/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,10 @@ for (const [caseId, label] of STARTUP_CASES) {
const baseline = moduleResults?.find(r => r.case === caseId && r.target === 'baseline')
const head = moduleResults?.find(r => r.case === caseId && r.target === 'head')
if (baseline && head) {
headlineRows.push([`${label} modules loaded`, String(baseline.modules), String(head.modules), formatDelta(baseline.modules, head.modules)])
headlineRows.push(
[`${label} modules loaded`, String(baseline.modules), String(head.modules), formatDelta(baseline.modules, head.modules)],
[`${label} built-ins loaded`, String(baseline.builtins), String(head.builtins), formatDelta(baseline.builtins, head.builtins)],
)
}
}
if (footprintResults) {
Expand Down
11 changes: 8 additions & 3 deletions bench/suites/modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export interface ModuleCount {
modules: number
nodeModules: number
bytes: number
/** Node.js built-ins loaded after bootstrap, internals included. */
builtins: number
}

export async function modulesSuite(targets: Target[], cwd: string): Promise<{ results: ModuleCount[], markdown: string }> {
Expand All @@ -36,7 +38,7 @@ export async function modulesSuite(targets: Target[], cwd: string): Promise<{ re
if (!match) {
throw new Error(`no module stats for ${target.id} ${testCase.id}:\n${result.stderr.slice(-2000)}`)
}
const parsed = JSON.parse(match[1]!) as { modules: number, nodeModules: number, bytes: number }
const parsed = JSON.parse(match[1]!) as { modules: number, nodeModules: number, bytes: number, builtins: number }
const entry = { case: testCase.id, target: target.id, ...parsed }
results.push(entry)
perTarget.push(entry)
Expand All @@ -50,14 +52,17 @@ export async function modulesSuite(targets: Target[], cwd: string): Promise<{ re
formatBytes(baseline!.bytes),
formatBytes(head!.bytes),
formatDelta(baseline!.bytes, head!.bytes),
String(baseline!.builtins),
String(head!.builtins),
formatDelta(baseline!.builtins, head!.builtins),
])
}

const markdown = [
'Counted with a `module.registerHooks` load hook, compile cache disabled. Counts every JS module actually evaluated on that code path (built-ins excluded, native addons excluded).',
'Counted with a `module.registerHooks` load hook, compile cache disabled. Counts every JS module actually evaluated on that code path (native addons excluded). Built-ins loaded after bootstrap are counted separately, including the internal modules they load.',
'',
markdownTable(
['Command', `${shortLabel(targets[0]!)} modules`, `${shortLabel(targets[1]!)} modules`, 'Delta', `${shortLabel(targets[0]!)} source bytes`, `${shortLabel(targets[1]!)} source bytes`, 'Delta'],
['Command', `${shortLabel(targets[0]!)} modules`, `${shortLabel(targets[1]!)} modules`, 'Delta', `${shortLabel(targets[0]!)} source bytes`, `${shortLabel(targets[1]!)} source bytes`, 'Delta', `${shortLabel(targets[0]!)} built-ins`, `${shortLabel(targets[1]!)} built-ins`, 'Delta'],
rows,
),
].join('\n')
Expand Down
15 changes: 7 additions & 8 deletions packages/nuxt-cli/src/commands/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { defineCommand } from 'citty'

import { openBrowser } from '../dev/listen'
import { releaseStdin, withDirectStdout } from '../utils/console'
import { DOCS_BASE_URL, DOCS_PATH, resolveDocsIndex } from '../utils/docs-index'
import { logger } from '../utils/logger'
import { resolveRootDir } from '../utils/paths'
import { withSpinner } from '../utils/spinner'
Expand Down Expand Up @@ -37,9 +36,10 @@ export default defineCommand({
},
},
async run(ctx) {
const { DOCS_BASE_URL, DOCS_PATH, resolveDocsIndex } = await import('../utils/docs-index')
const query = ctx.args._.join(' ').trim()
if (!query) {
return visit(DOCS_PATH, ctx.args.open)
return visit(DOCS_BASE_URL + DOCS_PATH, ctx.args.open)
}

const found = await withSpinner(`Searching the Nuxt documentation for ${styleText('cyan', query)}`, async (spinner) => {
Expand All @@ -58,13 +58,13 @@ export default defineCommand({

if (!found) {
logger.warn(`Could not read the Nuxt documentation for this project. Run again with ${styleText('cyan', 'DEBUG=nuxi*')} to see why.`)
return visit(DOCS_PATH, ctx.args.open)
return visit(DOCS_BASE_URL + DOCS_PATH, ctx.args.open)
}

const { index, results } = found
if (results.length === 0) {
logger.warn(`Nothing in the Nuxt ${index.version} documentation matches ${styleText('cyan', query)}.`)
return visit(DOCS_PATH, ctx.args.open)
return visit(DOCS_BASE_URL + DOCS_PATH, ctx.args.open)
}

if (results.length > 1 && ctx.args.open && isInteractive()) {
Expand All @@ -82,7 +82,7 @@ export default defineCommand({
cancel(`Nuxt documentation: ${DOCS_BASE_URL}${index.base}${results[0]!.path}`)
return
}
return visit(index.base + choice, true)
return visit(DOCS_BASE_URL + index.base + choice, true)
}

const width = Math.max(...results.map(entry => entry.title.length))
Expand All @@ -92,12 +92,11 @@ export default defineCommand({
})
process.stdout.write(`${lines.join('\n')}\n`)

return visit(index.base + results[0]!.path, ctx.args.open)
return visit(DOCS_BASE_URL + index.base + results[0]!.path, ctx.args.open)
},
})

function visit(path: string, open: boolean): void {
const url = DOCS_BASE_URL + path
function visit(url: string, open: boolean): void {
logger.info(`${open ? 'Opening' : 'Nuxt documentation:'} ${styleText('cyan', url)}`)
if (open) {
openBrowser(url)
Expand Down
2 changes: 1 addition & 1 deletion packages/nuxt-cli/src/commands/info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { writeText } from 'tinyclip'
import { version as nuxiVersion } from '../../package.json'

import { getBuilder } from '../utils/banner'
import { resolveCatalogEntry } from '../utils/catalog'
import { printJson } from '../utils/console'
import { formatInfoBox } from '../utils/formatting'
import { logger } from '../utils/logger'
Expand Down Expand Up @@ -186,6 +185,7 @@ async function resolveDependencyVersion(
return pkg.version
}
}
const { resolveCatalogEntry } = await import('../utils/catalog')
return resolveCatalogEntry(cwd, projectPkg, name)?.specifier
?? dependencies[name]
?? devDependencies[name]
Expand Down
2 changes: 1 addition & 1 deletion packages/nuxt-cli/src/commands/module/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import { createInstallLog, isVerboseInstall, runInstall, takeUnreportedIgnoredBu
import { logger } from '../../utils/logger'
import { logNetworkError } from '../../utils/network'
import { defaultPackageManager, detectPackageManager, isPackageManagerName, packageManagerNames } from '../../utils/package-managers'
import { detectNpmRegistry } from '../../utils/registry'
import { createSpinner } from '../../utils/spinner'
import { getNuxtVersion } from '../../utils/versions'
import { cwdArgs, logLevelArgs } from '../_shared'
Expand Down Expand Up @@ -403,6 +402,7 @@ async function resolveModule(moduleName: string, cwd: string, modulesDB: NuxtMod

let version = pkgVersion || 'latest'
const pkgScope = pkgName.startsWith('@') ? pkgName.split('/')[0]! : null
const { detectNpmRegistry } = await import('../../utils/registry')
const meta = await detectNpmRegistry(pkgScope, cwd)
const headers: HeadersInit = meta.authorization ? { Authorization: meta.authorization } : {}

Expand Down
3 changes: 1 addition & 2 deletions packages/nuxt-cli/src/commands/upgrade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { cancel, isCancel, note, select } from '@clack/prompts'
import { defineCommand } from 'citty'
import { dirname, relative, resolve } from 'pathe'
import { findWorkspaceDir, readPackageJSON } from 'pkg-types'
import { resolveCatalogEntry, updateCatalogEntries } from '../utils/catalog'

import { createInstallLog, runDedupe, runInstall, takeUnreportedIgnoredBuilds } from '../utils/install'
import { loadKit } from '../utils/kit'
Expand Down Expand Up @@ -173,7 +172,7 @@ export default defineCommand({

intro(styleText('cyan', 'Upgrading Nuxt ...'))

const [packageManager, workspaceDir = cwd] = await Promise.all([detectPackageManager(cwd), findWorkspaceDir(cwd, { try: true })])
const [packageManager, workspaceDir = cwd, { resolveCatalogEntry, updateCatalogEntries }] = await Promise.all([detectPackageManager(cwd), findWorkspaceDir(cwd, { try: true }), import('../utils/catalog')])
if (!packageManager) {
logger.error(
`Unable to determine the package manager used by this project.\n\nNo lock files found in ${styleText('cyan', relativeToProcess(cwd))}, and no ${styleText('cyan', 'packageManager')} field specified in ${styleText('cyan', 'package.json')}.`,
Expand Down
8 changes: 6 additions & 2 deletions packages/nuxt-cli/src/dev/tui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ import { styleText } from 'node:util'
import { resolveStackVersions } from '../../utils/banner'
import { withDirectStdout } from '../../utils/console'
import { formatDuration, terminalLink } from '../../utils/formatting'
import { releaseNotesUrl } from '../../utils/release-notes'
import { startupElapsedMs } from '../../utils/startup-clock'

import { registerTerminalHost } from '../../utils/terminal-host'
import { MUTED, paint } from '../../utils/terminal-theme'
import { checkForUpdate, isUpdateCheckEnabled, releaseNotesUrl } from '../../utils/update-check'
import { openBrowser, writeClipboard } from '../listen'
import { setupShortcuts } from '../shortcuts'
import { isShutdownAdopted } from '../shutdown'
Expand Down Expand Up @@ -843,10 +843,14 @@ async function resolveQRCode(context: ShortcutContext): Promise<string | undefin

/** The newer Nuxt release, if the registry knows of one and checks are enabled. */
async function resolveUpdate(current?: string): Promise<string | undefined> {
if (!current || !isUpdateCheckEnabled()) {
if (!current) {
return undefined
}
try {
const { checkForUpdate, isUpdateCheckEnabled } = await import('../../utils/update-check')
if (!isUpdateCheckEnabled()) {
return undefined
}
const update = await checkForUpdate('nuxt', current)
return update?.latest
}
Expand Down
17 changes: 11 additions & 6 deletions packages/nuxt-cli/src/utils/network.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import http from 'node:http'
import process from 'node:process'

import { styleText } from 'node:util'
Expand Down Expand Up @@ -40,7 +39,10 @@ export type ProxySetupResult = 'unused' | 'active' | 'children-only'

type EnableGlobalProxy = (env: NodeJS.ProcessEnv) => unknown

const setGlobalProxyFromEnv = (http as { setGlobalProxyFromEnv?: EnableGlobalProxy }).setGlobalProxyFromEnv
// `node:http` loads tls, crypto, http2 and undici, so it stays off the startup path.
function getGlobalProxySetter(): EnableGlobalProxy | null {
return (process.getBuiltinModule('node:http') as { setGlobalProxyFromEnv?: EnableGlobalProxy }).setGlobalProxyFromEnv ?? null
}

let envProxyActive: boolean | undefined
let proxyHintShown = false
Expand All @@ -49,17 +51,20 @@ let proxyHintShown = false
* Route requests from this process and its children through the proxy
* environment variables, and record whether this process is proxy-aware.
*/
export function setupProxySupport(env: NodeJS.ProcessEnv = process.env, enableGlobalProxy: EnableGlobalProxy | null = setGlobalProxyFromEnv ?? null): ProxySetupResult {
export function setupProxySupport(env: NodeJS.ProcessEnv = process.env, enableGlobalProxy?: EnableGlobalProxy | null): ProxySetupResult {
proxyHintShown = false

if (!hasProxyEnv(env)) {
envProxyActive = undefined
return 'unused'
}
envProxyActive = isEnvProxyActive(env)
if (!envProxyActive && enableGlobalProxy && env.NODE_USE_ENV_PROXY !== '0') {
enableGlobalProxy(env)
envProxyActive = true
if (!envProxyActive && env.NODE_USE_ENV_PROXY !== '0') {
const enable = enableGlobalProxy === undefined ? getGlobalProxySetter() : enableGlobalProxy
if (enable) {
enable(env)
envProxyActive = true
}
}
env.NODE_USE_ENV_PROXY ||= '1'

Expand Down
2 changes: 1 addition & 1 deletion packages/nuxt-cli/src/utils/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { homedir } from 'node:os'
import { join } from 'node:path'
import process from 'node:process'

import { parseINI } from 'confbox'
import { parseINI } from 'confbox/ini'

const TRAILING_SLASH_RE = /\/$/
const ENV_REFERENCE_RE = /\$\{([^}]+)\}/g
Expand Down
19 changes: 19 additions & 0 deletions packages/nuxt-cli/src/utils/release-notes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/** Repositories whose releases are published under a `v`-prefixed tag. */
const RELEASE_REPOS: Record<string, string> = {
'nuxt': 'nuxt/nuxt',
'@nuxt/cli': 'nuxt/cli',
'nuxi': 'nuxt/cli',
'create-nuxt': 'nuxt/cli',
}

/**
* The release notes for a published version, when the package has a known
* repository. Nightlies are skipped: their versions have no matching tag.
*/
export function releaseNotesUrl(pkg: string, version: string): string | undefined {
const repo = RELEASE_REPOS[pkg]
if (!repo || version.includes('nightly')) {
return undefined
}
return `https://github.com/${repo}/releases/tag/v${version}`
}
21 changes: 1 addition & 20 deletions packages/nuxt-cli/src/utils/update-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { fetchJson } from './fetch'
import { terminalLink } from './formatting'
import { debug, writeNotice } from './logger'
import { detectNpmRegistry } from './registry'
import { releaseNotesUrl } from './release-notes'
import { trackOutputSpacing } from './stdout'

const RC_FILE = '.nuxtrc'
Expand Down Expand Up @@ -148,26 +149,6 @@ export interface UpdateNudgeOptions {
pkg?: string
}

/** Repositories whose releases are published under a `v`-prefixed tag. */
const RELEASE_REPOS: Record<string, string> = {
'nuxt': 'nuxt/nuxt',
'@nuxt/cli': 'nuxt/cli',
'nuxi': 'nuxt/cli',
'create-nuxt': 'nuxt/cli',
}

/**
* The release notes for a published version, when the package has a known
* repository. Nightlies are skipped: their versions have no matching tag.
*/
export function releaseNotesUrl(pkg: string, version: string): string | undefined {
const repo = RELEASE_REPOS[pkg]
if (!repo || version.includes('nightly')) {
return undefined
}
return `https://github.com/${repo}/releases/tag/v${version}`
}

function describeUpdate({ current, latest }: NuxtUpdate, name: string, pkg: string): string {
const notes = releaseNotesUrl(pkg, latest)
const version = styleText('green', latest)
Expand Down
5 changes: 2 additions & 3 deletions packages/nuxt-cli/src/utils/versions.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { readPackageJSON } from 'pkg-types'
import { coerce, findMaxSatisfying, normalize } from 'verkit'

import { resolveCatalogEntry } from './catalog'
import { fetchJson } from './fetch'
import { debug } from './logger'
import { NUXT_PACKAGES } from './nuxt-packages'
import { readDependencyPackageJson } from './package-json'
import { detectNpmRegistry, PUBLIC_REGISTRY } from './registry'

/** How long to wait on the registry before giving up on a version lookup. */
const FETCH_TIMEOUT = 10_000
Expand All @@ -27,7 +25,7 @@ export async function getNuxtVersion(cwd: string) {
if (nuxtPkg?.version && NUXT_PACKAGE_NAMES.has(nuxtPkg.name!)) {
return nuxtPkg.version
}
const pkg = await readPackageJSON(cwd)
const [pkg, { resolveCatalogEntry }] = await Promise.all([readPackageJSON(cwd), import('./catalog')])
const pkgDep = resolveCatalogEntry(cwd, pkg, 'nuxt')?.specifier
?? (pkg?.dependencies?.nuxt || pkg?.devDependencies?.nuxt)
const coerced = pkgDep && coerce(pkgDep)
Expand All @@ -41,6 +39,7 @@ export async function getNuxtVersion(cwd: string) {
*/
export async function resolveRegistryVersion(pkg: string, range: string): Promise<string | undefined> {
const scope = pkg.startsWith('@') ? pkg.split('/')[0]! : null
const { detectNpmRegistry, PUBLIC_REGISTRY } = await import('./registry')
const { registry, authorization } = await detectNpmRegistry(scope)

const packument = await fetchPackument(pkg, registry, authorization)
Expand Down
2 changes: 1 addition & 1 deletion packages/nuxt-cli/test/unit/utils/release-notes.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest'

import { terminalLink } from '../../../src/utils/formatting'
import { releaseNotesUrl } from '../../../src/utils/update-check'
import { releaseNotesUrl } from '../../../src/utils/release-notes'

describe('release notes links', () => {
it('points at the tag for packages with a known repository', () => {
Expand Down
Loading