Skip to content

Commit

Permalink
fix(prune) pnpm patch name incorrectness (vercel#3176)
Browse files Browse the repository at this point in the history
We had an incorrect assumption about how patches would be named in the
`pnpm` section in `package.json`.

Added some unit tests using the fixtures from the reproduction provided
in vercel#2788
  • Loading branch information
chris-olszewski authored Jan 23, 2023
1 parent 3edef0f commit 20a17dd
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 4 deletions.
25 changes: 21 additions & 4 deletions cli/internal/lockfile/pnpm_lockfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package lockfile
import (
"fmt"
"io"
"sort"
"strings"

"github.com/pkg/errors"
"github.com/vercel/turbo/cli/internal/turbopath"
Expand Down Expand Up @@ -259,7 +261,16 @@ func prunePatches(patches map[string]PatchFile, packages map[string]PackageSnaps

patchPackages := make(map[string]PatchFile, len(patches))
for dependency, entry := range patches {
dependencyString := fmt.Sprintf("%s_%s", dependency, entry.Hash)
// The name for patches is of the form name@version
// https://github.com/pnpm/pnpm/blob/2895389ae1f2bf7346e140c017f495aa47186eba/packages/plugin-commands-patching/src/patchCommit.ts#L38
lastAt := strings.LastIndex(dependency, "@")
if lastAt == -1 {
panic(fmt.Sprintf("No '@' found in patch key: %s", dependency))
}
name := strings.Replace(dependency[:lastAt], "/", "-", 1)
version := dependency[lastAt+1:]

dependencyString := fmt.Sprintf("%s_%s", formatPnpmKey(name, version), entry.Hash)
_, inPackages := packages[dependencyString]
if inPackages {
patchPackages[dependency] = entry
Expand Down Expand Up @@ -289,13 +300,19 @@ func (p *PnpmLockfile) Patches() []turbopath.AnchoredUnixPath {
if len(p.PatchedDependencies) == 0 {
return nil
}
patches := make([]turbopath.AnchoredUnixPath, len(p.PatchedDependencies))
patches := make([]string, len(p.PatchedDependencies))
i := 0
for _, patch := range p.PatchedDependencies {
patches[i] = turbopath.AnchoredUnixPath(patch.Path)
patches[i] = patch.Path
i++
}
return patches
sort.Strings(patches)

patchPaths := make([]turbopath.AnchoredUnixPath, len(p.PatchedDependencies))
for i, patch := range patches {
patchPaths[i] = turbopath.AnchoredUnixPath(patch)
}
return patchPaths
}

func (p *PnpmLockfile) resolveSpecifier(workspacePath turbopath.AnchoredUnixPath, name string, specifier string) (string, bool, error) {
Expand Down
29 changes: 29 additions & 0 deletions cli/internal/lockfile/pnpm_lockfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,32 @@ func Test_DecodePnpmUnquotedURL(t *testing.T) {
assert.NilError(t, err, "valid package entry should be able to be decoded")
assert.Equal(t, resolution["tarball"], "path/to/tarball?foo=bar")
}

func Test_PnpmLockfilePatches(t *testing.T) {
contents, err := getFixture(t, "pnpm-patch.yaml")
assert.NilError(t, err)

lockfile, err := DecodePnpmLockfile(contents)
assert.NilError(t, err)

patches := lockfile.Patches()
assert.Equal(t, len(patches), 2)
assert.Equal(t, patches[0], turbopath.AnchoredUnixPath("patches/@[email protected]"))
assert.Equal(t, patches[1], turbopath.AnchoredUnixPath("patches/[email protected]"))
}

func Test_PnpmPrunePatches(t *testing.T) {
contents, err := getFixture(t, "pnpm-patch.yaml")
assert.NilError(t, err)

lockfile, err := DecodePnpmLockfile(contents)
assert.NilError(t, err)

prunedLockfile, err := lockfile.Subgraph(
[]turbopath.AnchoredSystemPath{turbopath.AnchoredSystemPath("packages/dependency")},
[]string{"/is-odd/3.0.1_nrrwwz7lemethtlvvm75r5bmhq", "/is-number/6.0.0", "/@babel-core/7.20.12_3hyn7hbvzkemudbydlwjmrb65y"},
)
assert.NilError(t, err)

assert.Equal(t, len(prunedLockfile.Patches()), 2)
}
49 changes: 49 additions & 0 deletions cli/internal/lockfile/testdata/pnpm-patch.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
lockfileVersion: 5.4

patchedDependencies:
[email protected]:
hash: nrrwwz7lemethtlvvm75r5bmhq
path: patches/[email protected]
"@babel/[email protected]":
hash: 3hyn7hbvzkemudbydlwjmrb65y
path: patches/@[email protected]

importers:
.:
specifiers: {}

packages/dependency:
specifiers:
is-odd: ^3.0.1
"@babel/core": ^7.20.12
dependencies:
is-odd: 3.0.1_nrrwwz7lemethtlvvm75r5bmhq
"@babel/core": 7.20.12_3hyn7hbvzkemudbydlwjmrb65y

packages:
/@babel-core/7.20.12_3hyn7hbvzkemudbydlwjmrb65y:
resolution:
{
integrity: sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==,
}
engines: { node: ">=6.9.0" }
dev: false

/is-number/6.0.0:
resolution:
{
integrity: sha512-Wu1VHeILBK8KAWJUAiSZQX94GmOE45Rg6/538fKwiloUu21KncEkYGPqob2oSZ5mUT73vLGrHQjKw3KMPwfDzg==,
}
engines: { node: ">=0.10.0" }
dev: false

/is-odd/3.0.1_nrrwwz7lemethtlvvm75r5bmhq:
resolution:
{
integrity: sha512-CQpnWPrDwmP1+SMHXZhtLtJv90yiyVfluGsX5iNCVkrhQtU3TQHsUWPG9wkdk9Lgd5yNpAg9jQEo90CBaXgWMA==,
}
engines: { node: ">=4" }
dependencies:
is-number: 6.0.0
dev: false
patched: true
13 changes: 13 additions & 0 deletions cli/internal/packagemanager/fixtures/pnpm-patches.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "turborepo-prune-removes-patched",
"version": "1.0.0",
"packageManager": "[email protected]",
"workspaces": [
"packages/*"
],
"pnpm": {
"patchedDependencies": {
"[email protected]": "patches/[email protected]"
}
}
}
57 changes: 57 additions & 0 deletions cli/internal/packagemanager/pnpm_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package packagemanager

import (
"os"
"testing"

"github.com/vercel/turbo/cli/internal/fs"
"github.com/vercel/turbo/cli/internal/turbopath"
"gotest.tools/v3/assert"
)

func pnpmPatchesSection(t *testing.T, pkgJSON *fs.PackageJSON) map[string]interface{} {
t.Helper()
pnpmSection, ok := pkgJSON.RawJSON["pnpm"].(map[string]interface{})
assert.Assert(t, ok)
patchesSection, ok := pnpmSection["patchedDependencies"].(map[string]interface{})
assert.Assert(t, ok)
return patchesSection
}

func getPnpmPackageJSON(t *testing.T) *fs.PackageJSON {
t.Helper()
rawCwd, err := os.Getwd()
assert.NilError(t, err)
cwd, err := fs.CheckedToAbsoluteSystemPath(rawCwd)
assert.NilError(t, err)
pkgJSONPath := cwd.Join("fixtures", "pnpm-patches.json")
pkgJSON, err := fs.ReadPackageJSON(pkgJSONPath)
assert.NilError(t, err)
return pkgJSON
}

func Test_PnpmPrunePatches_KeepsNecessary(t *testing.T) {
pkgJSON := getPnpmPackageJSON(t)
initialPatches := pnpmPatchesSection(t, pkgJSON)

assert.DeepEqual(t, initialPatches, map[string]interface{}{"[email protected]": "patches/[email protected]"})

err := pnpmPrunePatches(pkgJSON, []turbopath.AnchoredUnixPath{turbopath.AnchoredUnixPath("patches/[email protected]")})
assert.NilError(t, err)

newPatches := pnpmPatchesSection(t, pkgJSON)
assert.DeepEqual(t, newPatches, map[string]interface{}{"[email protected]": "patches/[email protected]"})
}

func Test_PnpmPrunePatches_RemovesExtra(t *testing.T) {
pkgJSON := getPnpmPackageJSON(t)
initialPatches := pnpmPatchesSection(t, pkgJSON)

assert.DeepEqual(t, initialPatches, map[string]interface{}{"[email protected]": "patches/[email protected]"})

err := pnpmPrunePatches(pkgJSON, nil)
assert.NilError(t, err)

newPatches := pnpmPatchesSection(t, pkgJSON)
assert.DeepEqual(t, newPatches, map[string]interface{}{})
}

0 comments on commit 20a17dd

Please sign in to comment.