Repository: pulumi/esc
Pull request #535 (2025-05-06 18:33:05)

chore: update to fx/v2
The newer version of this module adds map- and slice-specific packages
to ease some of the boilerplate (e.g. `slices.Map(s, ...)` can replace
`fx.Map(fx.IterSlice(s), ...))`.

These changes also fix some etag vs. revision tag conflation in the CLI
tests.



Patch:
diff --git a/go.mod b/go.mod
index e707110..9297e9d 100644
--- a/go.mod
+++ b/go.mod
@@ -17,7 +17,7 @@ require (
 	github.com/hexops/gotextdiff v1.0.3
 	github.com/muesli/termenv v0.15.2
 	github.com/petar-dambovaliev/aho-corasick v0.0.0-20230725210150-fb29fc3c913e
-	github.com/pgavlin/fx v0.1.6
+	github.com/pgavlin/fx/v2 v2.0.3
 	github.com/pulumi/pulumi/pkg/v3 v3.154.0
 	github.com/pulumi/pulumi/sdk/v3 v3.154.0
 	github.com/rivo/uniseg v0.4.4
@@ -161,6 +161,7 @@ require (
 	github.com/opentracing/basictracer-go v1.1.0 // indirect
 	github.com/opentracing/opentracing-go v1.2.0 // indirect
 	github.com/pgavlin/diff v0.0.0-20230503175810-113847418e2e // indirect
+	github.com/pgavlin/fx v0.1.6 // indirect
 	github.com/pgavlin/goldmark v1.1.33-0.20200616210433-b5eb04559386 // indirect
 	github.com/pgavlin/text v0.0.0-20240821195002-b51d0990e284 // indirect
 	github.com/pjbgf/sha1cd v0.3.0 // indirect
diff --git a/go.sum b/go.sum
index 932c4f2..3a8933a 100644
--- a/go.sum
+++ b/go.sum
@@ -400,6 +400,8 @@ github.com/pgavlin/diff v0.0.0-20230503175810-113847418e2e h1:Or25BtWLCyWKjnLyuM
 github.com/pgavlin/diff v0.0.0-20230503175810-113847418e2e/go.mod h1:WGwlmuPAiQTGQUjxyAfP7j4JgbgiFvFpI/qRtsQtS/4=
 github.com/pgavlin/fx v0.1.6 h1:r9jEg69DhNoCd3Xh0+5mIbdbS3PqWrVWujkY76MFRTU=
 github.com/pgavlin/fx v0.1.6/go.mod h1:KWZJ6fqBBSh8GxHYqwYCf3rYE7Gp2p0N8tJp8xv9u9M=
+github.com/pgavlin/fx/v2 v2.0.3 h1:ZBVklTFjxcWvBVPE+ti5qwnmTIQ0Gq6nuj3J5RKDtKk=
+github.com/pgavlin/fx/v2 v2.0.3/go.mod h1:Cvnwqq0BopdHUJ7CU50h1XPeKrF4ZwdFj1nJLXbAjCE=
 github.com/pgavlin/goldmark v1.1.33-0.20200616210433-b5eb04559386 h1:LoCV5cscNVWyK5ChN/uCoIFJz8jZD63VQiGJIRgr6uo=
 github.com/pgavlin/goldmark v1.1.33-0.20200616210433-b5eb04559386/go.mod h1:MRxHTJrf9FhdfNQ8Hdeh9gmHevC9RJE/fu8M3JIGjoE=
 github.com/pgavlin/text v0.0.0-20240821195002-b51d0990e284 h1:qpLdAFg3kyV/mEsuMPBgLzFo3xRpKBdOff8m0up9eAs=


Test patch:
diff --git a/cmd/esc/cli/cli_test.go b/cmd/esc/cli/cli_test.go
index a34354e..6675f06 100644
--- a/cmd/esc/cli/cli_test.go
+++ b/cmd/esc/cli/cli_test.go
@@ -16,6 +16,7 @@ import (
 	"os/exec"
 	"path"
 	"path/filepath"
+	"slices"
 	"sort"
 	"strconv"
 	"strings"
@@ -24,7 +25,8 @@ import (
 	"time"
 
 	"github.com/gofrs/uuid"
-	"github.com/pgavlin/fx"
+	"github.com/pgavlin/fx/v2"
+	"github.com/pgavlin/fx/v2/maps"
 	"github.com/pulumi/esc"
 	"github.com/pulumi/esc/cmd/esc/cli/client"
 	"github.com/pulumi/esc/eval"
@@ -38,8 +40,6 @@ import (
 	"github.com/spf13/cobra"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
-	"golang.org/x/exp/maps"
-	"golang.org/x/exp/slices"
 	"gopkg.in/yaml.v3"
 	"mvdan.cc/sh/v3/expand"
 	"mvdan.cc/sh/v3/interp"
@@ -266,6 +266,7 @@ type testEnvironmentRetract struct {
 type testEnvironmentRevision struct {
 	number    int
 	yaml      []byte
+	etag      string
 	tags      map[string]bool
 	retracted *testEnvironmentRetract
 }
@@ -491,11 +492,8 @@ func (c *testPulumiClient) ListOrganizationEnvironments(
 	orgName string,
 	continuationToken string,
 ) ([]client.OrgEnvironment, string, error) {
-	names := maps.Keys(c.environments)
-	sort.Strings(names)
-
 	var envs []client.OrgEnvironment
-	for _, k := range names {
+	for k := range maps.Sorted(c.environments) {
 		parts := strings.Split(k, "/")
 		org, projectName, envName := parts[0], parts[1], parts[2]
 
@@ -595,12 +593,7 @@ func (c *testPulumiClient) GetEnvironment(
 		yaml = plaintext
 	}
 
-	eTag := ""
-	if len(env.tags) > 0 {
-		eTag = maps.Keys(env.tags)[0]
-	}
-
-	return yaml, eTag, env.number, nil
+	return yaml, env.etag, env.number, nil
 }
 
 func (c *testPulumiClient) UpdateEnvironment(
@@ -631,16 +624,15 @@ func (c *testPulumiClient) UpdateEnvironmentWithRevision(
 	projectName string,
 	envName string,
 	yaml []byte,
-	tag string,
+	etag string,
 ) ([]client.EnvironmentDiagnostic, int, error) {
 	env, latest, err := c.getEnvironment(orgName, projectName, envName, "")
 	if err != nil {
 		return nil, 0, err
 	}
 
-	latestRevHasTag := latest.tags[tag]
-	if tag != "" && !latestRevHasTag {
-		return nil, 0, errors.New("tag mismatch")
+	if etag != "" && etag != latest.etag {
+		return nil, 0, errors.New("etag mismatch")
 	}
 
 	_, diags, err := c.checkEnvironment(ctx, orgName, envName, yaml, nil)
@@ -657,8 +649,13 @@ func (c *testPulumiClient) UpdateEnvironmentWithRevision(
 		env.revisions = append(env.revisions, &testEnvironmentRevision{
 			number: revisionNumber,
 			yaml:   yaml,
-			tags:   map[string]bool{base64.StdEncoding.EncodeToString(h.Sum(nil)): true},
+			etag:   base64.StdEncoding.EncodeToString(h.Sum(nil)),
+			tags:   map[string]bool{"latest": true},
 		})
+
+		if n, ok := env.revisionTags["latest"]; ok && n > 0 {
+			delete(env.revisions[n-1].tags, "latest")
+		}
 		env.revisionTags["latest"] = revisionNumber
 	}
 
@@ -1071,10 +1068,8 @@ func (c *testPulumiClient) ListEnvironmentRevisionTags(
 		return nil, err
 	}
 
-	names := maps.Keys(env.revisionTags)
-	slices.Sort(names)
-	return fx.ToSlice(fx.FMap(fx.IterSlice(names), func(name string) (client.EnvironmentRevisionTag, bool) {
-		return client.EnvironmentRevisionTag{Name: name, Revision: env.revisionTags[name]}, name > options.After
+	return slices.Collect(fx.FMap(maps.SortedPairs(env.revisionTags), func(kvp fx.Pair[string, int]) (client.EnvironmentRevisionTag, bool) {
+		return client.EnvironmentRevisionTag{Name: kvp.Fst, Revision: kvp.Snd}, kvp.Fst > options.After
 	})), nil
 }
 
@@ -1336,20 +1331,25 @@ func loadTestcase(path string) (*cliTestcaseYAML, *cliTestcase, error) {
 				}
 			}
 
+			h := fnv.New32()
+			h.Write(bytes)
+
 			revisionNumber := len(envRevisions) + 1
-			envRevisions = append(envRevisions, &testEnvironmentRevision{
+			r := &testEnvironmentRevision{
 				number:    revisionNumber,
 				yaml:      bytes,
 				retracted: retract,
+				etag:      base64.StdEncoding.EncodeToString(h.Sum(nil)),
 				tags:      map[string]bool{},
-			})
+			}
+			envRevisions = append(envRevisions, r)
 
 			for _, rt := range rev.Tags {
 				if _, ok := revisionTags[rt]; ok || rt == "latest" {
 					return nil, nil, fmt.Errorf("duplicate tag %q", rt)
 				}
 				revisionTags[rt] = revisionNumber
-				envRevisions[revisionNumber-1].tags[rt] = true
+				r.tags[rt] = true
 			}
 		}
 		revisionTags["latest"] = len(envRevisions)
diff --git a/eval/eval_test.go b/eval/eval_test.go
index ee87446..0040eee 100644
--- a/eval/eval_test.go
+++ b/eval/eval_test.go
@@ -28,7 +28,10 @@ import (
 	"testing"
 	"time"
 
-	"github.com/pgavlin/fx"
+	"github.com/pgavlin/fx/v2"
+	fxm "github.com/pgavlin/fx/v2/maps"
+	fxs "github.com/pgavlin/fx/v2/slices"
+	"github.com/pgavlin/fx/v2/try"
 	"github.com/pulumi/esc"
 	"github.com/pulumi/esc/schema"
 	"github.com/pulumi/esc/syntax"
@@ -267,10 +270,10 @@ func newBenchEnvironments(root string, delay time.Duration) (*benchEnvironments,
 		return nil, err
 	}
 
-	defs, err := fx.TryMap(fx.Map(
-		fx.FMap(fx.IterSlice(entries), func(e os.DirEntry) (fx.Pair[string, string], bool) {
+	defs, err := fxm.TryCollect(try.UnpackAll(fx.Map(
+		fxs.FMap(entries, func(e os.DirEntry) (fx.Pair[string, string], bool) {
 			name, ok := strings.CutSuffix(e.Name(), ".yaml")
-			return fx.NewPair(name, filepath.Join(root, e.Name())), ok
+			return fx.Pack(name, filepath.Join(root, e.Name())), ok
 		}),
 		func(namepath fx.Pair[string, string]) fx.Result[fx.Pair[string, []byte]] {
 			name, path := namepath.Unpack()
@@ -278,9 +281,10 @@ func newBenchEnvironments(root string, delay time.Duration) (*benchEnvironments,
 			if err != nil {
 				return fx.Err[fx.Pair[string, []byte]](err)
 			}
-			return fx.OK(fx.NewPair(name, bytes))
-		}),
-	)
+			return fx.OK(fx.Pack(name, bytes))
+
+		},
+	)))
 	if err != nil {
 		return nil, err
 	}