Files
pnpm/installing/deps-resolver/test/updateProjectManifest.test.ts
morning-verlu 531f2a307c fix: preserve workspace specs on update (#12140)
## What
- preserve existing `workspace:` dependency specifiers when `updateProjectManifest` saves updated direct dependencies and `preserveWorkspaceProtocol` is enabled
- keep catalog specifiers taking precedence over resolver-normalized specs
- add focused coverage for preserved and normalized local spec behavior
- add a changeset for the published `@pnpm/installing.deps-resolver` change

### pacquet parity
Ported the same fix to pacquet's `update` command. Previously `pacquet update --latest` routed every direct dependency through a registry `latest` lookup, so a `workspace:` local-path dependency (e.g. `workspace:../packages/foo/dist`) was rewritten into a registry version — corrupting the manifest (in the regression test it became `0.0.1-security`). Both `--latest` rewrite sites now skip registry resolution for such specs via `is_workspace_local_path_specifier`, a faithful port of pnpm's `isWorkspaceLocalPathSpecifier`. The gate is unconditional in the `--latest` path because `preserveWorkspaceProtocol` is always on there (its only override derives from `linkWorkspacePackages` under `--workspace`, which cannot be combined with `--latest`).

Fixes #3902

---------

Co-authored-by: morning-verlu <258725120+morning-verlu@users.noreply.github.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-06-16 23:38:05 +00:00

101 lines
3.1 KiB
TypeScript

import { expect, test } from '@jest/globals'
import type { PkgResolutionId, ProjectId, ProjectRootDir } from '@pnpm/types'
import type { ImporterToResolve } from '../lib/index.js'
import type { ResolvedDirectDependency } from '../lib/resolveDependencyTree.js'
import { updateProjectManifest } from '../lib/updateProjectManifest.js'
test('updateProjectManifest preserves workspace protocol specs when requested', async () => {
const [manifest] = await updateProjectManifest(createImporter('workspace:../packages/foo/dist'), {
directDependencies: [createDirectDependency()],
preserveWorkspaceProtocol: true,
saveWorkspaceProtocol: 'rolling',
})
expect(manifest?.dependencies?.foo).toBe('workspace:../packages/foo/dist')
})
test('updateProjectManifest saves normalized local specs when workspace protocol is not preserved', async () => {
const [manifest] = await updateProjectManifest(createImporter('workspace:../packages/foo/dist'), {
directDependencies: [createDirectDependency()],
preserveWorkspaceProtocol: false,
saveWorkspaceProtocol: 'rolling',
})
expect(manifest?.dependencies?.foo).toBe('link:../packages/foo/dist')
})
test('updateProjectManifest saves normalized workspace range specs', async () => {
const [manifest] = await updateProjectManifest(createImporter('workspace:*'), {
directDependencies: [
createDirectDependency({
normalizedBareSpecifier: 'workspace:^1.0.0',
}),
],
preserveWorkspaceProtocol: true,
saveWorkspaceProtocol: 'rolling',
})
expect(manifest?.dependencies?.foo).toBe('workspace:^1.0.0')
})
test('updateProjectManifest preserves catalog specifier precedence', async () => {
const [manifest] = await updateProjectManifest(createImporter('workspace:../packages/foo/dist'), {
directDependencies: [
createDirectDependency({
catalogLookup: {
catalogName: 'default',
specifier: '^1.0.0',
userSpecifiedBareSpecifier: 'catalog:',
},
}),
],
preserveWorkspaceProtocol: true,
saveWorkspaceProtocol: 'rolling',
})
expect(manifest?.dependencies?.foo).toBe('catalog:')
})
function createImporter (bareSpecifier: string): ImporterToResolve {
return {
binsDir: '/project/node_modules/.bin',
id: '.' as ProjectId,
manifest: {
dependencies: {
foo: bareSpecifier,
},
},
modulesDir: '/project/node_modules',
rootDir: '/project' as ProjectRootDir,
targetDependenciesField: 'dependencies',
updatePackageManifest: true,
wantedDependencies: [
{
alias: 'foo',
bareSpecifier,
dev: false,
optional: false,
updateSpec: true,
},
],
}
}
function createDirectDependency (overrides: Partial<ResolvedDirectDependency> = {}): ResolvedDirectDependency {
return {
alias: 'foo',
dev: false,
name: 'foo',
normalizedBareSpecifier: 'link:../packages/foo/dist',
optional: false,
pkgId: 'link:../packages/foo/dist' as PkgResolutionId,
resolution: {
directory: '../packages/foo/dist',
type: 'directory',
},
version: '1.0.0',
...overrides,
}
}