Skip to content

fix(nuxi): resolve upgrade lockfile across workspace and app cwd#1230

Open
samiashi wants to merge 1 commit intonuxt:mainfrom
samiashi:samielachi/fix-upgrade-lockfile-detection
Open

fix(nuxi): resolve upgrade lockfile across workspace and app cwd#1230
samiashi wants to merge 1 commit intonuxt:mainfrom
samiashi:samielachi/fix-upgrade-lockfile-detection

Conversation

@samiashi
Copy link

🔗 Linked issue

Closes #951

❓ Type of change

  • 📖 Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

This fixes nuxi upgrade lockfile resolution in monorepo layouts where the app directory and workspace directory differ.

upgrade previously tried to normalize lockfile candidates against a single directory (workspaceDir). In some setups this causes Unable to find any lock files... even when a valid lockfile exists for the current app context.

Changes:

  • Update lockfile lookup in packages/nuxi/src/commands/upgrade.ts to search both workspaceDir and the command cwd.
  • Keep package-manager-provided lockfile candidate logic unchanged.
  • Improve the error message to show all searched directories when no lockfile is found.
  • Add regression tests in packages/nuxi/test/unit/commands/upgrade.spec.ts:
    • resolves lockfile from fallback app directory
    • returns undefined + logs error when no lockfile exists in any searched directory

Validation:

  • pnpm lint
  • pnpm vitest --run packages/nuxi/test/unit/commands
  • pnpm test:unit

@samiashi samiashi requested a review from danielroe as a code owner February 17, 2026 12:39
@github-actions
Copy link
Contributor

github-actions bot commented Feb 17, 2026

📦 Bundle Size Comparison

📈 nuxi

Metric Base Head Diff
Rendered 3695.54 KB 3695.85 KB +0.31 KB (+0.01%)

📈 nuxt-cli

Metric Base Head Diff
Rendered 138.81 KB 139.00 KB +0.19 KB (+0.14%)

➡️ create-nuxt

Metric Base Head Diff
Rendered 1644.95 KB 1644.95 KB 0.00 KB (0.00%)

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 17, 2026

  • nuxt-cli-playground

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

commit: 5c00643

@codecov-commenter
Copy link

codecov-commenter commented Feb 17, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (main@9face47). Learn more about missing BASE report.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1230   +/-   ##
=======================================
  Coverage        ?   47.68%           
=======================================
  Files           ?       47           
  Lines           ?     1187           
  Branches        ?      348           
=======================================
  Hits            ?      566           
  Misses          ?      510           
  Partials        ?      111           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@codspeed-hq
Copy link

codspeed-hq bot commented Feb 17, 2026

Merging this PR will not alter performance

✅ 2 untouched benchmarks


Comparing samiashi:samielachi/fix-upgrade-lockfile-detection (5c00643) with main (9face47)

Open in CodSpeed

@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

Warning

Rate limit exceeded

@samiashi has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 2 minutes and 5 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

normaliseLockFile was changed to accept either a single directory or an array of directories (cwds) and was exported. The logic now normalizes cwds to a unique array and searches for lockfiles across all provided directories rather than a single directory. Error messaging when no lockfile is found was updated to list all searched directories. The upgrade command wiring was adjusted to pass multiple directories (workspace and project) into the new multi-directory lockfile resolution. Unit tests were added to cover the new behaviours and various input scenarios.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: resolving upgrade lockfile detection across both workspace and app directories in monorepo scenarios.
Description check ✅ Passed The description is related to the changeset and clearly explains the bug fix, changes made, and validation performed for the lockfile resolution issue.
Linked Issues check ✅ Passed The PR successfully addresses issue #951 by enabling lockfile detection across both workspace and app directories, resolving the monorepo lockfile resolution failure described in the issue.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the lockfile resolution issue: updating normaliseLockFile signature, implementing multi-directory search, and adding comprehensive regression tests.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
packages/nuxi/test/unit/commands/upgrade.spec.ts (2)

1-47: Good regression tests covering the core monorepo scenario.

The mocking strategy is clean, and the two tests cover the primary use case (lockfile found in a fallback directory) and the error path. A few optional additional cases could strengthen coverage:

  • lockFiles is undefined (verifies the ?.find short-circuit)
  • cwds passed as a single string (verifies the normalization branch)
  • Duplicate directories in cwds (verifies Set dedup)
  • Lockfile found in the first directory (workspace root) — the most common monorepo case

These are non-blocking and can be deferred.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nuxi/test/unit/commands/upgrade.spec.ts` around lines 1 - 47, Add
the suggested extra unit tests to strengthen coverage around normaliseLockFile:
add one test where lockFiles is undefined to exercise the optional chaining
short-circuit, one test passing cwds as a single string to validate
normalization, one test with duplicate entries in cwds to ensure deduplication
(Set) works, and one test where the lockfile is found in the first directory to
cover the common workspace-root case; reuse the existing vi.mock setup
(existsSync and loggerError) and the same describe/beforeEach pattern so you
only add new it(...) blocks that set existsSync.mockImplementation or
mockReturnValue and assert the returned value and loggerError calls.

11-15: Mock for node:fs doesn't re-export other members.

The node:fs mock only re-exports existsSync. If other code imported alongside normaliseLockFile (or transitive imports at module evaluation time) references other node:fs exports, this could break. Currently safe since normaliseLockFile only uses existsSync, but spreading the original module is a more resilient pattern:

Suggested improvement
 vi.mock('node:fs', async () => {
+  const actual = await vi.importActual<typeof import('node:fs')>('node:fs')
   return {
+    ...actual,
     existsSync,
   }
 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nuxi/test/unit/commands/upgrade.spec.ts` around lines 11 - 15, The
test's vi.mock for 'node:fs' only returns existsSync which can break other
imports; update the mock in the test so it imports the real module and spreads
its exports (preserving all original members) while overriding existsSync as
needed — modify the vi.mock callback used for 'node:fs' (the mock around
existsSync used by normaliseLockFile) to return { ...originalFs, existsSync }
instead of only { existsSync }.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/nuxi/test/unit/commands/upgrade.spec.ts`:
- Around line 1-47: Add the suggested extra unit tests to strengthen coverage
around normaliseLockFile: add one test where lockFiles is undefined to exercise
the optional chaining short-circuit, one test passing cwds as a single string to
validate normalization, one test with duplicate entries in cwds to ensure
deduplication (Set) works, and one test where the lockfile is found in the first
directory to cover the common workspace-root case; reuse the existing vi.mock
setup (existsSync and loggerError) and the same describe/beforeEach pattern so
you only add new it(...) blocks that set existsSync.mockImplementation or
mockReturnValue and assert the returned value and loggerError calls.
- Around line 11-15: The test's vi.mock for 'node:fs' only returns existsSync
which can break other imports; update the mock in the test so it imports the
real module and spreads its exports (preserving all original members) while
overriding existsSync as needed — modify the vi.mock callback used for 'node:fs'
(the mock around existsSync used by normaliseLockFile) to return {
...originalFs, existsSync } instead of only { existsSync }.

@samiashi samiashi force-pushed the samielachi/fix-upgrade-lockfile-detection branch 3 times, most recently from 81bd6d6 to 127ec70 Compare February 17, 2026 12:59
@samiashi
Copy link
Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

✅ Actions performed

Review triggered.

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (4)
packages/nuxi/test/unit/commands/upgrade.spec.ts (4)

166-173: Consider asserting the error message content.

The test verifies loggerError was called once but doesn't assert the message. A snapshot or string-match assertion on the logged message (e.g., that both /workspace and /apps/web appear) would guard against regressions in the user-facing error output, which is part of the fix's scope.

💡 Suggested assertion addition
     expect(lockFile).toBeUndefined()
     expect(loggerError).toHaveBeenCalledTimes(1)
+    expect(loggerError).toHaveBeenCalledWith(
+      expect.stringContaining('/workspace'),
+    )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nuxi/test/unit/commands/upgrade.spec.ts` around lines 166 - 173, The
test currently only checks that loggerError was called once when
normaliseLockFile(['/workspace','/apps/web'], ['pnpm-lock.yaml']) returns
undefined; update the assertion to also verify the logged error message content
by inspecting loggerError.mock.calls (or using toHaveBeenCalledWith / string
match) to assert it includes both '/workspace' and '/apps/web' (or take a
snapshot of the message) so the user-facing error text is covered; reference the
normaliseLockFile invocation and loggerError mock and keep existsSync mocked
false as-is.

211-226: Integration test validates the key wiring but is fragile to internal refactors.

The test asserts on specific existsSync call arguments ('/workspace/package-lock.json' and '/apps/web/package-lock.json'), which couples it to the internal resolution order and the exact resolve() output. If the implementation changes how paths are joined or the search order, this test breaks even if behavior is correct.

A couple of observations:

  1. The test doesn't assert the return value or outcome of upgradeCommand.run. Adding a check (e.g., that loggerSuccess was called, or that addDependency was invoked) would confirm the command completed successfully after finding the lockfile.

  2. The select mock has no configured return value (defaults to undefined). This works only because channel: 'stable' is passed in args, presumably skipping the prompt. If the command's internal flow changes to always prompt, this test would silently pass with unexpected behavior. Consider explicitly mocking select.mockResolvedValue('stable') to be resilient.

💡 Suggested hardening
   it('checks both workspace and project directories during upgrade command run', async () => {
     existsSync.mockImplementation((filePath: string) => filePath.endsWith('/apps/web/package-lock.json'))
+    select.mockResolvedValue('stable')

     await upgradeCommand.run!({
       args: {
         cwd: '/apps/web',
         rootDir: '/apps/web',
         dedupe: true,
         force: false,
         channel: 'stable',
       },
     } as any)

     expect(existsSync).toHaveBeenCalledWith('/workspace/package-lock.json')
     expect(existsSync).toHaveBeenCalledWith('/apps/web/package-lock.json')
+    expect(addDependency).toHaveBeenCalled()
   })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nuxi/test/unit/commands/upgrade.spec.ts` around lines 211 - 226, The
test is fragile because it asserts exact existsSync paths and doesn't verify
command outcome or prompt behavior; update the test that calls
upgradeCommand.run to (1) replace strict path assertions with assertions that
existsSync was called for two paths that end with 'package-lock.json' (e.g., use
matchers like expect.stringMatching(/package-lock\.json$/) or expect.any(String)
and validate endsWith), (2) explicitly mock the prompt by adding
select.mockResolvedValue('stable'), and (3) assert a visible outcome such as
loggerSuccess being called or addDependency being invoked to confirm the
upgradeCommand.run flow completed (referencing upgradeCommand.run, existsSync,
select, loggerSuccess, addDependency).

137-155: beforeEach configures mocks primarily for the integration test; consider scoping them.

The mocks for detectPackageManager, findWorkspaceDir, readPackageJSON, getNuxtVersion, addDependency, dedupeDependencies, cleanupNuxtDirs, loadKit, and getPackageManagerVersion are only exercised by the final integration test (line 211). For the pure normaliseLockFile unit tests (lines 157–209), only existsSync and loggerError matter. Moving these heavier mocks into a nested describe or the integration test's own setup would make each test's dependencies clearer and avoid confusion if a unit test accidentally triggers an upgrade-command code path.

That said, it's harmless as-is and vi.clearAllMocks() keeps state clean.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nuxi/test/unit/commands/upgrade.spec.ts` around lines 137 - 155, The
current beforeEach registers many heavy mocks (detectPackageManager,
findWorkspaceDir, readPackageJSON, getNuxtVersion, addDependency,
dedupeDependencies, cleanupNuxtDirs, loadKit, getPackageManagerVersion) that are
only needed by the integration test exercising the upgrade path, while the unit
tests for normaliseLockFile only need existsSync and loggerError; refactor by
keeping a lightweight outer beforeEach that only sets up existsSync and
loggerError (and vi.clearAllMocks()), and move the heavier mocks into a nested
describe or the integration test's own beforeEach so tests that call
normaliseLockFile only depend on minimal mocks and the integration test still
configures detectPackageManager, findWorkspaceDir, readPackageJSON,
getNuxtVersion, addDependency, dedupeDependencies, cleanupNuxtDirs, loadKit, and
getPackageManagerVersion.

184-191: Verify undefined lockFiles path exercises the intended guard.

When lockFiles is undefined, the implementation does lockFiles?.find(...) which short-circuits to undefined without iterating any directories. This means existsSync is never called, yet the test also sets existsSync.mockReturnValue(false). This is fine but worth noting: the test validates the outcome (returns undefined, logs error) but doesn't assert that existsSync was not called — which would strengthen the test by confirming the optional-chaining guard works.

💡 Optional extra assertion
     expect(lockFile).toBeUndefined()
     expect(loggerError).toHaveBeenCalledTimes(1)
+    expect(existsSync).not.toHaveBeenCalled()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nuxi/test/unit/commands/upgrade.spec.ts` around lines 184 - 191,
Update the unit test to also assert that existsSync was not called to verify the
optional-chaining guard; specifically, in the spec that calls
normaliseLockFile(['/workspace', '/apps/web'], undefined) add an expectation
that existsSync (the mocked function) hasBeenCalledTimes(0) (or
toHaveBeenCalledWith not called) and keep the existing assertions on lockFile
being undefined and loggerError being called once to ensure the guard
short-circuits as intended.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/nuxi/test/unit/commands/upgrade.spec.ts`:
- Around line 166-173: The test currently only checks that loggerError was
called once when normaliseLockFile(['/workspace','/apps/web'],
['pnpm-lock.yaml']) returns undefined; update the assertion to also verify the
logged error message content by inspecting loggerError.mock.calls (or using
toHaveBeenCalledWith / string match) to assert it includes both '/workspace' and
'/apps/web' (or take a snapshot of the message) so the user-facing error text is
covered; reference the normaliseLockFile invocation and loggerError mock and
keep existsSync mocked false as-is.
- Around line 211-226: The test is fragile because it asserts exact existsSync
paths and doesn't verify command outcome or prompt behavior; update the test
that calls upgradeCommand.run to (1) replace strict path assertions with
assertions that existsSync was called for two paths that end with
'package-lock.json' (e.g., use matchers like
expect.stringMatching(/package-lock\.json$/) or expect.any(String) and validate
endsWith), (2) explicitly mock the prompt by adding
select.mockResolvedValue('stable'), and (3) assert a visible outcome such as
loggerSuccess being called or addDependency being invoked to confirm the
upgradeCommand.run flow completed (referencing upgradeCommand.run, existsSync,
select, loggerSuccess, addDependency).
- Around line 137-155: The current beforeEach registers many heavy mocks
(detectPackageManager, findWorkspaceDir, readPackageJSON, getNuxtVersion,
addDependency, dedupeDependencies, cleanupNuxtDirs, loadKit,
getPackageManagerVersion) that are only needed by the integration test
exercising the upgrade path, while the unit tests for normaliseLockFile only
need existsSync and loggerError; refactor by keeping a lightweight outer
beforeEach that only sets up existsSync and loggerError (and
vi.clearAllMocks()), and move the heavier mocks into a nested describe or the
integration test's own beforeEach so tests that call normaliseLockFile only
depend on minimal mocks and the integration test still configures
detectPackageManager, findWorkspaceDir, readPackageJSON, getNuxtVersion,
addDependency, dedupeDependencies, cleanupNuxtDirs, loadKit, and
getPackageManagerVersion.
- Around line 184-191: Update the unit test to also assert that existsSync was
not called to verify the optional-chaining guard; specifically, in the spec that
calls normaliseLockFile(['/workspace', '/apps/web'], undefined) add an
expectation that existsSync (the mocked function) hasBeenCalledTimes(0) (or
toHaveBeenCalledWith not called) and keep the existing assertions on lockFile
being undefined and loggerError being called once to ensure the guard
short-circuits as intended.

@samiashi samiashi force-pushed the samielachi/fix-upgrade-lockfile-detection branch from 127ec70 to 5c00643 Compare February 17, 2026 13:37
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.

Run pnpm nuxt upgrade in monorepo

2 participants