mirror of
https://github.com/pnpm/pnpm.git
synced 2026-06-28 09:55:39 -04:00
## 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>
77 lines
2.7 KiB
TypeScript
77 lines
2.7 KiB
TypeScript
import {
|
|
type PackageSpecObject,
|
|
updateProjectManifestObject,
|
|
} from '@pnpm/pkg-manifest.utils'
|
|
import type { ProjectManifest } from '@pnpm/types'
|
|
|
|
import type { ImporterToResolve } from './index.js'
|
|
import type { ResolvedDirectDependency } from './resolveDependencyTree.js'
|
|
|
|
export async function updateProjectManifest (
|
|
importer: ImporterToResolve,
|
|
opts: {
|
|
directDependencies: ResolvedDirectDependency[]
|
|
preserveWorkspaceProtocol: boolean
|
|
saveWorkspaceProtocol: boolean | 'rolling'
|
|
}
|
|
): Promise<Array<ProjectManifest | undefined>> {
|
|
if (!importer.manifest) {
|
|
throw new Error('Cannot save because no package.json found')
|
|
}
|
|
const specsToUpsert: PackageSpecObject[] = opts.directDependencies
|
|
.filter((rdd, index) => importer.wantedDependencies[index]?.updateSpec)
|
|
.map((rdd, index) => {
|
|
const wantedDep = importer.wantedDependencies[index]!
|
|
return {
|
|
alias: rdd.alias,
|
|
peer: importer.peer,
|
|
bareSpecifier: getBareSpecifierToSave(wantedDep, rdd, opts.preserveWorkspaceProtocol),
|
|
resolvedVersion: rdd.version,
|
|
pinnedVersion: importer.pinnedVersion,
|
|
saveType: importer.targetDependenciesField,
|
|
}
|
|
})
|
|
for (const pkgToInstall of importer.wantedDependencies) {
|
|
if (pkgToInstall.updateSpec && pkgToInstall.alias && !specsToUpsert.some(({ alias }) => alias === pkgToInstall.alias)) {
|
|
specsToUpsert.push({
|
|
alias: pkgToInstall.alias,
|
|
peer: importer.peer,
|
|
saveType: importer.targetDependenciesField,
|
|
})
|
|
}
|
|
}
|
|
const hookedManifest = await updateProjectManifestObject(
|
|
importer.rootDir,
|
|
importer.manifest,
|
|
specsToUpsert
|
|
)
|
|
const originalManifest = (importer.originalManifest != null)
|
|
? await updateProjectManifestObject(
|
|
importer.rootDir,
|
|
importer.originalManifest,
|
|
specsToUpsert
|
|
)
|
|
: undefined
|
|
return [hookedManifest, originalManifest]
|
|
}
|
|
|
|
function getBareSpecifierToSave (
|
|
wantedDep: ImporterToResolve['wantedDependencies'][number],
|
|
resolvedDep: ResolvedDirectDependency,
|
|
preserveWorkspaceProtocol: boolean
|
|
): string {
|
|
if (resolvedDep.catalogLookup != null) {
|
|
return resolvedDep.catalogLookup.userSpecifiedBareSpecifier
|
|
}
|
|
if (preserveWorkspaceProtocol && isWorkspaceLocalPathSpecifier(wantedDep.bareSpecifier)) {
|
|
return wantedDep.bareSpecifier
|
|
}
|
|
return resolvedDep.normalizedBareSpecifier ?? wantedDep.bareSpecifier
|
|
}
|
|
|
|
function isWorkspaceLocalPathSpecifier (bareSpecifier: string): boolean {
|
|
if (!bareSpecifier.startsWith('workspace:')) return false
|
|
const pref = bareSpecifier.slice('workspace:'.length)
|
|
return pref.startsWith('.') || pref.startsWith('/') || pref.startsWith('~/') || /^[A-Z]:/i.test(pref)
|
|
}
|