diff --git a/go.mod b/go.mod index c895bc92a..187943716 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.23.4 require ( github.com/Dynatrace/libbuildpack-dynatrace v1.9.0 github.com/Masterminds/semver v1.5.0 - github.com/cloudfoundry/libbuildpack v0.0.0-20260415084012-70e599bbe72c + github.com/cloudfoundry/libbuildpack v0.0.0-20261012122722-6cd465089938 github.com/cloudfoundry/switchblade v0.9.5 github.com/golang/mock v1.6.0 github.com/onsi/ginkgo/v2 v2.22.1 diff --git a/go.sum b/go.sum index 23687ccab..365667ed6 100644 --- a/go.sum +++ b/go.sum @@ -285,8 +285,6 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/CycloneDX/cyclonedx-go v0.7.1/go.mod h1:N/nrdWQI2SIjaACyyDs/u7+ddCkyl/zkNs8xFsHF2Ps= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= -github.com/Dynatrace/libbuildpack-dynatrace v1.8.0 h1:VNcd8+rurUUdY12emGfLGUUj5cMH4hkNgrdk8LO3dHE= -github.com/Dynatrace/libbuildpack-dynatrace v1.8.0/go.mod h1:Uu9aa5UFAk1Ua+zZXnvzo+avDXuEi+GtegeOyja9xg4= github.com/Dynatrace/libbuildpack-dynatrace v1.9.0 h1:3tJzXt7VVTsvPPS9Q7+e/KAk0ccC2eAbgHib5C/XRhA= github.com/Dynatrace/libbuildpack-dynatrace v1.9.0/go.mod h1:Uu9aa5UFAk1Ua+zZXnvzo+avDXuEi+GtegeOyja9xg4= github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible/go.mod h1:BB1eHdMLYEFuFdBlRMb0N7YGVdM5s6Pt0njxgvfbGGs= @@ -478,8 +476,8 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= -github.com/cloudfoundry/libbuildpack v0.0.0-20260415084012-70e599bbe72c h1:BMlBv4TunN2BTh8CgVL1Hf8iiKCCIk5eD64Dg9fU4GM= -github.com/cloudfoundry/libbuildpack v0.0.0-20260415084012-70e599bbe72c/go.mod h1:Qtj1XicpoDn88w2cvVCYtw1Whq+kK3bouin0xNZ9lIU= +github.com/cloudfoundry/libbuildpack v0.0.0-20261012122722-6cd465089938 h1:hKWGdn8UAnlVtkNG+HnvEK9f8s2NDTpq48/HxKd0SSc= +github.com/cloudfoundry/libbuildpack v0.0.0-20261012122722-6cd465089938/go.mod h1:Qtj1XicpoDn88w2cvVCYtw1Whq+kK3bouin0xNZ9lIU= github.com/cloudfoundry/switchblade v0.9.5 h1:GTga1Uu6kGOL+n1TRTHyZm170N5/B/ou6wU90MiKKys= github.com/cloudfoundry/switchblade v0.9.5/go.mod h1:hIEQdGAsuNnzlyQfsD5OIORt38weSBar6Wq5/JX6Omo= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= diff --git a/src/nodejs/supply/mocks_test.go b/src/nodejs/supply/mocks_test.go index 3aa86e885..a5595fd09 100644 --- a/src/nodejs/supply/mocks_test.go +++ b/src/nodejs/supply/mocks_test.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: src/nodejs/supply/supply.go +// Source: supply.go // Package supply_test is a generated GoMock package. package supply_test @@ -245,6 +245,43 @@ func (mr *MockYarnMockRecorder) Build(buildDir, cacheDir, isBerry interface{}) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Build", reflect.TypeOf((*MockYarn)(nil).Build), buildDir, cacheDir, isBerry) } +// MockPNPM is a mock of PNPM interface. +type MockPNPM struct { + ctrl *gomock.Controller + recorder *MockPNPMMockRecorder +} + +// MockPNPMMockRecorder is the mock recorder for MockPNPM. +type MockPNPMMockRecorder struct { + mock *MockPNPM +} + +// NewMockPNPM creates a new mock instance. +func NewMockPNPM(ctrl *gomock.Controller) *MockPNPM { + mock := &MockPNPM{ctrl: ctrl} + mock.recorder = &MockPNPMMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPNPM) EXPECT() *MockPNPMMockRecorder { + return m.recorder +} + +// Build mocks base method. +func (m *MockPNPM) Build(arg0, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Build", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Build indicates an expected call of Build. +func (mr *MockPNPMMockRecorder) Build(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Build", reflect.TypeOf((*MockPNPM)(nil).Build), arg0, arg1) +} + // MockStager is a mock of Stager interface. type MockStager struct { ctrl *gomock.Controller diff --git a/vendor/github.com/cloudfoundry/libbuildpack/installer.go b/vendor/github.com/cloudfoundry/libbuildpack/installer.go index ebcf2d406..dbbf34774 100644 --- a/vendor/github.com/cloudfoundry/libbuildpack/installer.go +++ b/vendor/github.com/cloudfoundry/libbuildpack/installer.go @@ -110,7 +110,7 @@ func (i *Installer) InstallDependencyWithStrip(dep Dependency, outputDir string, func (i *Installer) warnNewerPatch(dep Dependency) error { - v, err := semver.NewVersion(dep.Version) + v, err := semver.NewVersion(normalizeSemver(dep.Version)) if err != nil { return nil } @@ -131,6 +131,11 @@ func (i *Installer) warnNewerPatch(dep Dependency) error { latest, err := FindMatchingVersion(constraint, versions) if err != nil { + if normalizeSemver(dep.Version) != dep.Version { + // Versions with more than three fields were never checked before; + // don't let an unparsable sibling version fail their installation. + return nil + } return err } @@ -142,13 +147,13 @@ func (i *Installer) warnNewerPatch(dep Dependency) error { } func (i *Installer) warnEndOfLife(dep Dependency) error { - matchVersion := func(versionLine, depVersion string) bool { + matchSemver := func(versionLine, depVersion string) bool { return versionLine == depVersion } - v, err := semver.NewVersion(dep.Version) + v, err := semver.NewVersion(normalizeSemver(dep.Version)) if err == nil { - matchVersion = func(versionLine, depVersion string) bool { + matchSemver = func(versionLine, depVersion string) bool { constraint, err := semver.NewConstraint(versionLine) if err != nil { return false @@ -158,6 +163,16 @@ func (i *Installer) warnEndOfLife(dep Dependency) error { } } + matchVersion := func(versionLine, depVersion string) bool { + // Exact version lines such as "21.0.12+10" or "21.0.12.1" are matched + // like exact FindMatchingVersion constraints; semver would ignore the + // build number or fail to parse more than three fields. + if c, ok := isExactJEP322Constraint(versionLine); ok { + return matchesExactJEP322(c, depVersion) + } + return matchSemver(versionLine, depVersion) + } + for _, deprecation := range i.manifest.Deprecations { if deprecation.Name != dep.Name { continue diff --git a/vendor/github.com/cloudfoundry/libbuildpack/versions.go b/vendor/github.com/cloudfoundry/libbuildpack/versions.go index f510997e0..0b9f34bc9 100644 --- a/vendor/github.com/cloudfoundry/libbuildpack/versions.go +++ b/vendor/github.com/cloudfoundry/libbuildpack/versions.go @@ -2,7 +2,10 @@ package libbuildpack import ( "fmt" + "regexp" "sort" + "strconv" + "strings" semver2 "github.com/Masterminds/semver" semver1 "github.com/blang/semver" @@ -14,9 +17,163 @@ type versionWithOriginal struct { } type versionsWithOriginal []versionWithOriginal -func (v versionsWithOriginal) Len() int { return len(v) } -func (v versionsWithOriginal) Swap(i, j int) { v[i], v[j] = v[j], v[i] } -func (v versionsWithOriginal) Less(i, j int) bool { return v[i].version.LT(v[j].version) } +func (v versionsWithOriginal) Len() int { return len(v) } +func (v versionsWithOriginal) Swap(i, j int) { v[i], v[j] = v[j], v[i] } +func (v versionsWithOriginal) Less(i, j int) bool { + if v[i].version.EQ(v[j].version) { + return compareJEP322(v[i].original, v[j].original) < 0 + } + return v[i].version.LT(v[j].version) +} + +// jep322Regex matches numeric JEP 322 style versions: any number of numeric +// fields, optionally followed by a numeric build number, e.g. "21", "21.0.12", +// "21.0.12.1", "21.0.10.0.1", "21.0.12+10" or "21.0.12.1+1". +// See https://openjdk.org/jeps/322. +var jep322Regex = regexp.MustCompile(`^(\d+(?:\.\d+)*)(?:\+(\d+))?$`) + +type jep322Version struct { + fields []int + build int // -1 if absent +} + +// parseJEP322 parses a numeric JEP 322 style version. It returns false for +// anything else, e.g. versions with pre-release identifiers or non-numeric +// build metadata, which are left to the semver libraries. +func parseJEP322(ver string) (jep322Version, bool) { + m := jep322Regex.FindStringSubmatch(ver) + if m == nil { + return jep322Version{}, false + } + parts := strings.Split(m[1], ".") + v := jep322Version{fields: make([]int, len(parts)), build: -1} + for i, p := range parts { + n, err := strconv.Atoi(p) + if err != nil { + return jep322Version{}, false + } + v.fields[i] = n + } + if m[2] != "" { + n, err := strconv.Atoi(m[2]) + if err != nil { + return jep322Version{}, false + } + v.build = n + } + return v, true +} + +// compareFields compares numeric fields, treating missing fields as zero, +// so it reports "21.0.12" and "21.0.12.0" as equal. compareJEP322 breaks +// such ties by number of fields; matchesExactJEP322 requires equal counts. +func (a jep322Version) compareFields(b jep322Version) int { + for i := 0; i < len(a.fields) || i < len(b.fields); i++ { + var x, y int + if i < len(a.fields) { + x = a.fields[i] + } + if i < len(b.fields) { + y = b.fields[i] + } + if x != y { + if x < y { + return -1 + } + return 1 + } + } + return 0 +} + +// compareJEP322 is the tie-breaker for versions that are equal according to +// semver. It orders two numeric JEP 322 versions by all numeric fields +// (trailing zeros insignificant), then by numeric build number +// (absent < +1 < +2), and finally by number of fields so that ordering stays +// deterministic ("21.0.12" < "21.0.12.0"). Other versions (e.g. with +// non-numeric build metadata such as "21.0.12+foo") are equal to each other +// and sort before numeric JEP 322 versions, keeping the ordering transitive. +func compareJEP322(a, b string) int { + va, okA := parseJEP322(a) + vb, okB := parseJEP322(b) + if !okA || !okB { + switch { + case okA: + return 1 + case okB: + return -1 + default: + return 0 + } + } + if c := va.compareFields(vb); c != 0 { + return c + } + if va.build != vb.build { + if va.build < vb.build { + return -1 + } + return 1 + } + return len(va.fields) - len(vb.fields) +} + +// normalizeSemver truncates a numeric JEP 322 version with more than three +// fields (e.g. "21.0.12.1", "21.0.12.1+1" or "21.0.10.0.1") to its first three +// fields ("21.0.12") so that the semver libraries can parse it. The original +// string is preserved separately for output and ordering (see compareJEP322). +// All other strings, including 3-part versions with build metadata +// ("21.0.12+10") and pre-release versions ("8.0.100-preview.7.23376.3"), are +// returned unchanged. +func normalizeSemver(ver string) string { + if v, ok := parseJEP322(ver); ok && len(v.fields) > 3 { + return fmt.Sprintf("%d.%d.%d", v.fields[0], v.fields[1], v.fields[2]) + } + return ver +} + +// isExactJEP322Constraint reports whether constraint is an exact numeric +// version that the semver libraries cannot handle correctly as a constraint: +// more than three fields ("21.0.12.1") or an explicit build number +// ("21.0.12+10", "21.0.12.1+1"). +func isExactJEP322Constraint(constraint string) (jep322Version, bool) { + v, ok := parseJEP322(constraint) + if !ok || (len(v.fields) <= 3 && v.build < 0) { + return jep322Version{}, false + } + return v, true +} + +// matchesExactJEP322 reports whether ver equals the exact constraint c. The +// numeric fields must be identical, including their number ("21.0.12.0" does +// not match "21.0.12"). Without a build number in the constraint any build +// matches ("21.0.12.1" matches "21.0.12.1+1" and "21.0.12.1+2"); with one, +// the build must be equal. +func matchesExactJEP322(c jep322Version, ver string) bool { + v, ok := parseJEP322(ver) + if !ok || len(v.fields) != len(c.fields) || v.compareFields(c) != 0 { + return false + } + return c.build < 0 || v.build == c.build +} + +// matchExactJEP322 returns all versions matching the exact constraint, see +// matchesExactJEP322. +func matchExactJEP322(constraint string, c jep322Version, versions []string) ([]string, error) { + var matched []string + for _, ver := range versions { + if matchesExactJEP322(c, ver) { + matched = append(matched, ver) + } + } + if len(matched) == 0 { + return []string{}, fmt.Errorf("no match found for %s in %v", constraint, versions) + } + sort.SliceStable(matched, func(i, j int) bool { + return compareJEP322(matched[i], matched[j]) < 0 + }) + return matched, nil +} func FindMatchingVersion(constraint string, versions []string) (string, error) { vs, err := FindMatchingVersions(constraint, versions) @@ -27,6 +184,13 @@ func FindMatchingVersion(constraint string, versions []string) (string, error) { } func FindMatchingVersions(constraint string, versions []string) ([]string, error) { + // Short-circuit: exact JEP 322 version constraint. Neither blang/semver nor + // Masterminds/semver accept more than three fields, and both ignore build + // metadata when comparing. + if c, ok := isExactJEP322Constraint(constraint); ok { + return matchExactJEP322(constraint, c, versions) + } + matchedVersions, err := matchSemver1(constraint, versions) if err == nil { return matchedVersions, nil @@ -43,17 +207,16 @@ func matchSemver1(constraint string, versions []string) ([]string, error) { } for _, ver := range versions { - depVersion, err := semver1.Parse(ver) + depVersion, err := semver1.Parse(normalizeSemver(ver)) if err != nil { return []string{}, err } - versionWithOriginal := versionWithOriginal{ - original: ver, - version: depVersion, - } if versionConstraint(depVersion) { - depVersions = append(depVersions, versionWithOriginal) + depVersions = append(depVersions, versionWithOriginal{ + original: ver, + version: depVersion, + }) } } @@ -70,28 +233,40 @@ func matchSemver1(constraint string, versions []string) ([]string, error) { } func matchSemver2(constraint string, versions []string) ([]string, error) { - var depVersions []*semver2.Version + type versionEntry struct { + original string + parsed *semver2.Version + } + var depVersions []versionEntry versionConstraint, err := semver2.NewConstraint(constraint) if err != nil { return []string{}, err } for _, ver := range versions { - depVersion, err := semver2.NewVersion(ver) + depVersion, err := semver2.NewVersion(normalizeSemver(ver)) if err != nil { return []string{}, err } if versionConstraint.Check(depVersion) { - depVersions = append(depVersions, depVersion) + depVersions = append(depVersions, versionEntry{ + original: ver, + parsed: depVersion, + }) } } if len(depVersions) != 0 { - sort.Sort(semver2.Collection(depVersions)) + sort.Slice(depVersions, func(i, j int) bool { + if depVersions[i].parsed.Equal(depVersions[j].parsed) { + return compareJEP322(depVersions[i].original, depVersions[j].original) < 0 + } + return depVersions[i].parsed.LessThan(depVersions[j].parsed) + }) var vs []string - for _, depV := range depVersions { - vs = append(vs, depV.Original()) + for _, e := range depVersions { + vs = append(vs, e.original) } return vs, nil } diff --git a/vendor/modules.txt b/vendor/modules.txt index c104825bc..32d5b6109 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -20,7 +20,7 @@ github.com/blang/semver # github.com/cenkalti/backoff/v4 v4.3.0 ## explicit; go 1.18 github.com/cenkalti/backoff/v4 -# github.com/cloudfoundry/libbuildpack v0.0.0-20260415084012-70e599bbe72c +# github.com/cloudfoundry/libbuildpack v0.0.0-20261012122722-6cd465089938 ## explicit; go 1.22.5 github.com/cloudfoundry/libbuildpack github.com/cloudfoundry/libbuildpack/ansicleaner