Skip to content

feat: support pnpm catalogs#1397

Merged
danielroe merged 5 commits into
mainfrom
feat/pnpm-catalogs
Jul 26, 2026
Merged

feat: support pnpm catalogs#1397
danielroe merged 5 commits into
mainfrom
feat/pnpm-catalogs

Conversation

@danielroe

@danielroe danielroe commented Jul 26, 2026

Copy link
Copy Markdown
Member

🔗 Linked issue

resolves #866

📚 Description

this follows path taken by https://github.com/antfu-collective/ni in resolving/handling pnpm catalogs

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The Nuxt CLI now supports pnpm catalogs by reading workspace catalog definitions, resolving catalog-managed dependency versions through the npm registry, and updating catalog entries during upgrades. The info command and Nuxt version resolution use catalog values before package manifest fallbacks. Upgrade specifications support scoped and aliased forms, with catalog packages updated separately from direct installations. Unit tests cover catalog parsing, workspace resolution, registry version lookup, Nuxt version resolution, and upgrade-specifier handling.

Estimated code review effort: 4 (Complex) | ~45 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes add catalog parsing, workspace YAML handling, and version resolution for catalog-managed deps, including test coverage for anchors and aliases.
Out of Scope Changes check ✅ Passed The diff appears focused on pnpm catalog support and related tests without unrelated feature work.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title clearly summarizes the main change: adding pnpm catalog support.
Description check ✅ Passed The description is related to the change and explains the pnpm catalog approach used.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/pnpm-catalogs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Jul 26, 2026

Copy link
Copy Markdown
  • nuxt-cli-playground

    npm i https://pkg.pr.new/create-nuxt@1397
    
    npm i https://pkg.pr.new/nuxi@1397
    
    npm i https://pkg.pr.new/@nuxt/cli@1397
    

commit: adcefef

@codspeed-hq

codspeed-hq Bot commented Jul 26, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 2 untouched benchmarks


Comparing feat/pnpm-catalogs (adcefef) with main (d4d2ade)

Open in CodSpeed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
packages/nuxt-cli/test/unit/commands/upgrade.spec.ts (1)

64-90: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No test coverage for resolveCatalogSpecifier.

It decides the exact specifier string written into pnpm-workspace.yaml (plain ^version vs npm:target@^version for aliases), which is core to the catalog-write path. Mocking resolveRegistryVersion (e.g. via vi.mock) would let this be tested without a real network call, alongside the existing parseInstallSpec suite.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nuxt-cli/test/unit/commands/upgrade.spec.ts` around lines 64 - 90,
Add unit coverage for resolveCatalogSpecifier alongside the existing
parseInstallSpec suite, mocking resolveRegistryVersion to avoid network access.
Assert that regular packages produce ^version and aliased packages produce
npm:target@^version, covering both catalog-write specifier formats.
packages/nuxt-cli/src/utils/catalog.ts (1)

86-95: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

resolveCatalogEntry re-reads and re-parses pnpm-workspace.yaml on every call.

readCatalogConfig re-runs findPnpmWorkspaceYaml + readFileSync + full YAML parse each time it's invoked. Across a single command this adds up: upgrade.ts calls resolveCatalogEntry once per package name in its prep loop, versions.ts's getNuxtVersion calls it separately, and info.ts's getDepVersion calls it once per listed module — so a single nuxi upgrade/nuxi info run can re-parse the same file many times.

Consider caching the parsed CatalogConfig per cwd (or accepting an already-parsed config) so repeated lookups within one command invocation don't re-hit the filesystem.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nuxt-cli/src/utils/catalog.ts` around lines 86 - 95, Update
resolveCatalogEntry and the readCatalogConfig path to cache the parsed
CatalogConfig per cwd, reusing the cached result for repeated lookups during a
command invocation instead of repeatedly finding, reading, and parsing
pnpm-workspace.yaml. Preserve the existing catalog and specifier resolution
behavior, including undefined results when no catalog configuration exists.
packages/nuxt-cli/test/unit/utils/catalog.spec.ts (1)

7-7: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No direct test coverage for setCatalogEntry.

This is the only exported function that mutates pnpm-workspace.yaml, and it's what upgrade.ts relies on to persist resolved catalog versions. Given the existing temp-dir fixture pattern here, a test writing a workspace file, calling setCatalogEntry, and asserting the on-disk content (plus the hasChanged()-driven boolean return) would be low effort and directly protect this write path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nuxt-cli/test/unit/utils/catalog.spec.ts` at line 7, Add direct unit
coverage for the exported setCatalogEntry function in catalog.spec.ts. Follow
the existing temporary-directory fixture pattern to create a
pnpm-workspace.yaml, invoke setCatalogEntry, and assert both the persisted
on-disk catalog content and the boolean result driven by hasChanged(), including
unchanged and updated cases as appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/nuxt-cli/src/commands/upgrade.ts`:
- Around line 257-278: Use the boolean result returned by setCatalogEntry in the
catalogUpdates loop before adding an entry to updated. Only push the package
specifier when the catalog write succeeds; otherwise leave it out of the success
summary and preserve the existing unresolved handling for resolution failures.

In `@packages/nuxt-cli/src/utils/catalog.ts`:
- Around line 101-115: Update setCatalogEntry to wrap readFileSync and
parsePnpmWorkspaceYaml in the same try/catch pattern used by readCatalogConfig,
returning false when reading or parsing fails. Keep the existing file-not-found,
unchanged-workspace, write, and success behavior intact.

In `@packages/nuxt-cli/src/utils/versions.ts`:
- Around line 37-52: Update resolveRegistryVersion to pass a 10-second timeout
in the $fetch options, and replace the order-dependent findLast selection with
verkit.findMaxSatisfying over the packument version keys for semver-aware
highest-version resolution. Preserve the dist-tags lookup as the first-choice
result.

---

Nitpick comments:
In `@packages/nuxt-cli/src/utils/catalog.ts`:
- Around line 86-95: Update resolveCatalogEntry and the readCatalogConfig path
to cache the parsed CatalogConfig per cwd, reusing the cached result for
repeated lookups during a command invocation instead of repeatedly finding,
reading, and parsing pnpm-workspace.yaml. Preserve the existing catalog and
specifier resolution behavior, including undefined results when no catalog
configuration exists.

In `@packages/nuxt-cli/test/unit/commands/upgrade.spec.ts`:
- Around line 64-90: Add unit coverage for resolveCatalogSpecifier alongside the
existing parseInstallSpec suite, mocking resolveRegistryVersion to avoid network
access. Assert that regular packages produce ^version and aliased packages
produce npm:target@^version, covering both catalog-write specifier formats.

In `@packages/nuxt-cli/test/unit/utils/catalog.spec.ts`:
- Line 7: Add direct unit coverage for the exported setCatalogEntry function in
catalog.spec.ts. Follow the existing temporary-directory fixture pattern to
create a pnpm-workspace.yaml, invoke setCatalogEntry, and assert both the
persisted on-disk catalog content and the boolean result driven by hasChanged(),
including unchanged and updated cases as appropriate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f98b69cc-f46e-403d-99af-d8b60f94ed32

📥 Commits

Reviewing files that changed from the base of the PR and between 79a829d and a981612.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • packages/nuxt-cli/package.json
  • packages/nuxt-cli/src/commands/info.ts
  • packages/nuxt-cli/src/commands/upgrade.ts
  • packages/nuxt-cli/src/utils/catalog.ts
  • packages/nuxt-cli/src/utils/versions.ts
  • packages/nuxt-cli/test/unit/commands/upgrade.spec.ts
  • packages/nuxt-cli/test/unit/utils/catalog.spec.ts
  • packages/nuxt-cli/test/unit/utils/versions.spec.ts

Comment thread packages/nuxt-cli/src/commands/upgrade.ts
Comment thread packages/nuxt-cli/src/utils/catalog.ts Outdated
Comment thread packages/nuxt-cli/src/utils/versions.ts
@danielroe
danielroe force-pushed the feat/pnpm-catalogs branch from a981612 to 1b01417 Compare July 26, 2026 16:56
@danielroe

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/nuxt-cli/src/utils/catalog.ts`:
- Around line 106-114: Update resolveCatalogEntry to resolve the package
specifier across dependencies, devDependencies, optionalDependencies, and
peerDependencies, preserving the existing precedence and catalog lookup behavior
so catalog-managed packages in any supported section use the workspace catalog
value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1d088525-f2a7-4536-ab6b-8aa2465cbe0f

📥 Commits

Reviewing files that changed from the base of the PR and between a981612 and 0deaf68.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • packages/nuxt-cli/package.json
  • packages/nuxt-cli/src/commands/info.ts
  • packages/nuxt-cli/src/commands/upgrade.ts
  • packages/nuxt-cli/src/utils/catalog.ts
  • packages/nuxt-cli/src/utils/versions.ts
  • packages/nuxt-cli/test/unit/commands/upgrade.spec.ts
  • packages/nuxt-cli/test/unit/utils/catalog.spec.ts
  • packages/nuxt-cli/test/unit/utils/versions.spec.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/nuxt-cli/test/unit/commands/upgrade.spec.ts
  • packages/nuxt-cli/src/utils/versions.ts
  • packages/nuxt-cli/src/commands/info.ts
  • packages/nuxt-cli/src/commands/upgrade.ts

Comment on lines +106 to +114
export function resolveCatalogEntry(cwd: string, pkgJson: PackageJson | null | undefined, pkg: string): CatalogEntry | undefined {
const specifier = pkgJson?.dependencies?.[pkg] || pkgJson?.devDependencies?.[pkg]
const catalog = parseCatalogSpecifier(specifier)
if (!catalog) {
return undefined
}

const config = readCatalogConfig(cwd)
return { catalog, specifier: config?.catalogs[catalog]?.[pkg] }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C3 --glob '*.ts' \
  'resolveCatalogEntry\(|optionalDependencies|peerDependencies' \
  packages/nuxt-cli/src packages/nuxt-cli/test

Repository: nuxt/cli

Length of output: 14300


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- packages/nuxt-cli/src/utils/catalog.ts outline ---\n'
ast-grep outline packages/nuxt-cli/src/utils/catalog.ts --view expanded || true

printf '\n--- packages/nuxt-cli/src/utils/catalog.ts relevant lines ---\n'
sed -n '1,150p' packages/nuxt-cli/src/utils/catalog.ts | cat -n

printf '\n--- packages/nuxt-cli/src/utils/versions.ts ---\n'
sed -n '1,60p' packages/nuxt-cli/src/utils/versions.ts | cat -n

printf '\n--- pnpm catalog references in package manifests ---\n'
rg -n '"catalog:("' -g 'package.json' .

Repository: nuxt/cli

Length of output: 237


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- packages/nuxt-cli/src/utils/catalog.ts outline ---'
ast-grep outline packages/nuxt-cli/src/utils/catalog.ts --view expanded || true

printf '%s\n' ''
printf '%s\n' '--- packages/nuxt-cli/src/utils/catalog.ts relevant lines ---'
sed -n '1,150p' packages/nuxt-cli/src/utils/catalog.ts | cat -n

printf '%s\n' ''
printf '%s\n' '--- packages/nuxt-cli/src/utils/versions.ts ---'
sed -n '1,60p' packages/nuxt-cli/src/utils/versions.ts | cat -n

printf '%s\n' ''
printf '%s\n' '--- pnpm catalog references in package manifests ---'
rg -n '"catalog:("' -g 'package.json' . || true

Repository: nuxt/cli

Length of output: 10135


🌐 Web query:

pnpm catalog references dependency sections dependencies devDependencies peerDependencies optionalDependencies workspace.yaml

💡 Result:

In pnpm, catalogs are a workspace feature that allows you to define dependency version ranges centrally in your pnpm-workspace.yaml file and reference them across your workspace packages [1][2]. You can use the catalog: protocol to reference these versions in the following dependency sections within any package.json file in your workspace: - dependencies [3] - devDependencies [3] - peerDependencies [3][4] - optionalDependencies [3] Additionally, the catalog: protocol can be used within the overrides field inside your pnpm-workspace.yaml file [5][3]. How it works: 1. Define your catalogs in pnpm-workspace.yaml using either the singular catalog field (for the default catalog) or the plural catalogs field (for named catalogs) [1][2]. 2. Reference the catalog version in a package.json file using the catalog: protocol [1][3]. - For the default catalog, you can use the shorthand catalog: [1][6]. - For named catalogs, use the syntax catalog:<catalog_name> (e.g., catalog:react18) [1][6]. Example: In pnpm-workspace.yaml: catalog: react: ^18.3.1 catalogs: testing: vitest: ^2.0.0 In a package.json: { "dependencies": { "react": "catalog:" }, "devDependencies": { "vitest": "catalog:testing" } } You can also add dependencies to catalogs directly via the CLI using pnpm add --save-catalog or pnpm add --save-catalog-name <catalog_name> [7].

Citations:


Resolve catalog references from all dependency sections.

resolveCatalogEntry() currently ignores optionalDependencies and peerDependencies, but pnpm catalogs are valid in those sections too. This can make catalog-managed packages bypass the workspace catalog value, including in the Nuxt version fallback.

Proposed fix
-  const specifier = pkgJson?.dependencies?.[pkg] || pkgJson?.devDependencies?.[pkg]
+  const specifier = pkgJson?.dependencies?.[pkg]
+    ?? pkgJson?.devDependencies?.[pkg]
+    ?? pkgJson?.optionalDependencies?.[pkg]
+    ?? pkgJson?.peerDependencies?.[pkg]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function resolveCatalogEntry(cwd: string, pkgJson: PackageJson | null | undefined, pkg: string): CatalogEntry | undefined {
const specifier = pkgJson?.dependencies?.[pkg] || pkgJson?.devDependencies?.[pkg]
const catalog = parseCatalogSpecifier(specifier)
if (!catalog) {
return undefined
}
const config = readCatalogConfig(cwd)
return { catalog, specifier: config?.catalogs[catalog]?.[pkg] }
export function resolveCatalogEntry(cwd: string, pkgJson: PackageJson | null | undefined, pkg: string): CatalogEntry | undefined {
const specifier = pkgJson?.dependencies?.[pkg]
?? pkgJson?.devDependencies?.[pkg]
?? pkgJson?.optionalDependencies?.[pkg]
?? pkgJson?.peerDependencies?.[pkg]
const catalog = parseCatalogSpecifier(specifier)
if (!catalog) {
return undefined
}
const config = readCatalogConfig(cwd)
return { catalog, specifier: config?.catalogs[catalog]?.[pkg] }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nuxt-cli/src/utils/catalog.ts` around lines 106 - 114, Update
resolveCatalogEntry to resolve the package specifier across dependencies,
devDependencies, optionalDependencies, and peerDependencies, preserving the
existing precedence and catalog lookup behavior so catalog-managed packages in
any supported section use the workspace catalog value.

@danielroe
danielroe force-pushed the feat/pnpm-catalogs branch from 0deaf68 to adcefef Compare July 26, 2026 19:18
@danielroe
danielroe merged commit 04d6931 into main Jul 26, 2026
19 checks passed
@danielroe
danielroe deleted the feat/pnpm-catalogs branch July 26, 2026 19:29
@github-actions github-actions Bot mentioned this pull request Jul 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

add support for pnpm catalogs

1 participant