Skip to content

Commit d241676

Browse files
committed
Hook up new TS repoless sync and update sync/bisync commands
1 parent 0f9a3fd commit d241676

File tree

13 files changed

+175
-23
lines changed

13 files changed

+175
-23
lines changed

cmd/lekko/bisync.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func bisync(ctx context.Context, project *native.Project, lekkoPath, repoPath st
5959
case native.LangGo:
6060
_ = try.To1(sync.BisyncGo(ctx, lekkoPath, lekkoPath, repoPath))
6161
case native.LangTypeScript:
62-
try.To(sync.BisyncTS(lekkoPath, repoPath))
62+
try.To(sync.BisyncTS(ctx, lekkoPath, repoPath))
6363
default:
6464
return errors.New("unsupported language")
6565
}

cmd/lekko/gen.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func genGoCmd() *cobra.Command {
8282
var initMode bool
8383
cmd := &cobra.Command{
8484
Use: "go",
85-
Short: "generate Go library code from configs",
85+
Short: "generate Go library code from lekkos",
8686
RunE: func(cmd *cobra.Command, args []string) error {
8787
nlProject := try.To1(native.DetectNativeLang(""))
8888
if nlProject.Language != native.LangGo {
@@ -104,11 +104,11 @@ func genTSCmd() *cobra.Command {
104104
var lekkoPath string
105105
cmd := &cobra.Command{
106106
Use: "ts",
107-
Short: "generate typescript library code from configs",
107+
Short: "generate TypeScript library code from lekkos",
108108
RunE: func(cmd *cobra.Command, args []string) error {
109109
nlProject := try.To1(native.DetectNativeLang(""))
110110
if nlProject.Language != native.LangTypeScript {
111-
return errors.Errorf("not a Go project, detected %v instead", nlProject.Language)
111+
return errors.Errorf("not a TypeScript project, detected %v instead", nlProject.Language)
112112
}
113113
return genNative(cmd.Context(), nlProject, lekkoPath, repoPath, ns, false)
114114
},

cmd/lekko/repo.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,7 @@ func pullCmd() *cobra.Command {
490490

491491
switch nlProject.Language {
492492
case native.LangTypeScript:
493+
// TODO: Fix pull for TS, this command no longer exists and we have repoless sync/gen now
493494
tsPullCmd := exec.Command("npx", "lekko-repo-pull", "--lekko-dir", lekkoPath)
494495
output, err := tsPullCmd.CombinedOutput()
495496
fmt.Println(string(output))

cmd/lekko/sync.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ func syncCmd() *cobra.Command {
5454
Short: "sync code to config",
5555
}
5656
cmd.AddCommand(syncGoCmd())
57+
cmd.AddCommand(syncTSCmd())
5758
return cmd
5859
}
5960

@@ -90,6 +91,44 @@ func syncGoCmd() *cobra.Command {
9091
return cmd
9192
}
9293

94+
func syncTSCmd() *cobra.Command {
95+
var repoPath string
96+
cmd := &cobra.Command{
97+
Use: "ts filepath1 filepath2 ...",
98+
Short: "parse TypeScript files with lekkos to a local config repository",
99+
RunE: func(cmd *cobra.Command, args []string) error {
100+
ctx := cmd.Context()
101+
var err error
102+
if len(repoPath) == 0 {
103+
repoPath, err = repo.PrepareGithubRepo()
104+
if err != nil {
105+
return err
106+
}
107+
}
108+
var repoContents *featurev1beta1.RepositoryContents
109+
if len(args) > 0 {
110+
repoContents, err = sync.SyncTSFiles(args...)
111+
} else {
112+
dot, doterr := dotlekko.ReadDotLekko("")
113+
if doterr != nil {
114+
return doterr
115+
}
116+
repoContents, err = sync.SyncTS(dot.LekkoPath)
117+
}
118+
if err != nil {
119+
return err
120+
}
121+
err = sync.WriteContentsToLocalRepo(ctx, repoContents, repoPath)
122+
if err != nil {
123+
return err
124+
}
125+
return nil
126+
},
127+
}
128+
cmd.Flags().StringVarP(&repoPath, "repo-path", "r", "", "path to config repository, will use autodetect if not set")
129+
return cmd
130+
}
131+
93132
func anyToLekkoAny(a *anypb.Any) *featurev1beta1.Any {
94133
return &featurev1beta1.Any{
95134
TypeUrl: a.GetTypeUrl(),

pkg/gen/gen.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func GenNative(ctx context.Context, project *native.Project, lekkoPath, repoPath
8181
}
8282
for _, ns := range opts.Namespaces {
8383
outFilename := filepath.Join(absLekkoPath, ns+project.Language.Ext())
84-
try.To(genFormattedTS(ctx, repoPath, ns, outFilename))
84+
try.To(GenFormattedTS(ctx, repoPath, ns, outFilename))
8585
}
8686
return nil
8787
case native.LangGo:

pkg/gen/golang.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ func renderGoTemplate(templateBody string, fileName string, data any) (string, e
324324
}
325325

326326
// Generates public and private function files for the namespace as well as the overall client file.
327-
// Writes outputs to the output paths specified in the
327+
// Writes outputs to the output paths specified in the construction args.
328328
// TODO: since generator takes in whole repo contents now, could generate for all/filtered namespaces
329329
func (g *goGenerator) Gen(ctx context.Context, namespaceName string) (err error) {
330330
defer err2.Handle(&err)

pkg/gen/ts.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ func getTSParameters(d protoreflect.MessageDescriptor) string {
138138
}
139139

140140
// Pipe `GenTS` to prettier
141-
func genFormattedTS(ctx context.Context, repoPath, ns, outFilename string) error {
141+
func GenFormattedTS(ctx context.Context, repoPath, ns, outFilename string) error {
142142
prettierCmd := exec.Command("npx", "prettier", "--parser", "typescript")
143143
stdinPipe, err := prettierCmd.StdinPipe()
144144
if err != nil {

pkg/native/native.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,8 @@ func NativeLangFromExt(filename string) (Language, error) {
137137
return "", fmt.Errorf("unsupported file extension: %v", ext)
138138
}
139139

140-
func (l *Language) Ext() string {
141-
switch *l {
140+
func (l Language) Ext() string {
141+
switch l {
142142
case LangGo:
143143
return ".go"
144144
case LangTypeScript:

pkg/proto/proto.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"google.golang.org/protobuf/types/dynamicpb"
2727
"google.golang.org/protobuf/types/known/anypb"
2828
"google.golang.org/protobuf/types/known/durationpb"
29+
"google.golang.org/protobuf/types/known/emptypb"
2930
"google.golang.org/protobuf/types/known/structpb"
3031
"google.golang.org/protobuf/types/known/wrapperspb"
3132

@@ -172,3 +173,34 @@ func PrintMessageDescriptor(md protoreflect.MessageDescriptor, indentLevel int)
172173
sb.WriteString(fmt.Sprintf("%s}\n", indent))
173174
return sb.String(), nil
174175
}
176+
177+
// A custom resolver for unmarshalling that will replace unresolvable Anys with emptypb.Empty messages
178+
type IgnoreAnyResolver struct{}
179+
180+
func (r *IgnoreAnyResolver) FindMessageByName(message protoreflect.FullName) (protoreflect.MessageType, error) {
181+
mt, err := protoregistry.GlobalTypes.FindMessageByName(message)
182+
if err != nil {
183+
return nil, errors.Wrap(err, "name?")
184+
}
185+
return mt, nil
186+
}
187+
188+
func (r *IgnoreAnyResolver) FindMessageByURL(url string) (protoreflect.MessageType, error) {
189+
mt, err := protoregistry.GlobalTypes.FindMessageByURL(url)
190+
if errors.Is(err, protoregistry.NotFound) {
191+
e := &emptypb.Empty{}
192+
return e.ProtoReflect().Type(), nil
193+
}
194+
if err != nil {
195+
return nil, errors.Wrap(err, "url?")
196+
}
197+
return mt, nil
198+
}
199+
200+
func (r *IgnoreAnyResolver) FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error) {
201+
return protoregistry.GlobalTypes.FindExtensionByName(field)
202+
}
203+
204+
func (r *IgnoreAnyResolver) FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error) {
205+
return protoregistry.GlobalTypes.FindExtensionByNumber(message, field)
206+
}

pkg/sync/golang.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,7 @@ func BisyncGo(ctx context.Context, outputPath, lekkoPath, repoPath string) ([]st
5555
if err != nil {
5656
return nil, err
5757
}
58-
5958
// Traverse target path, finding namespaces
60-
// TODO: consider making this more efficient for batch gen/sync
6159
files := make([]string, 0)
6260
if err := filepath.WalkDir(lekkoPath, func(p string, d fs.DirEntry, err error) error {
6361
if err != nil {
@@ -67,7 +65,7 @@ func BisyncGo(ctx context.Context, outputPath, lekkoPath, repoPath string) ([]st
6765
if d.IsDir() && d.Name() == "proto" {
6866
return filepath.SkipDir
6967
}
70-
// Sync and gen - only target <namespace>/<namespace>.go files
68+
// Only target <namespace>/<namespace>.go files
7169
if !d.IsDir() && strings.TrimSuffix(d.Name(), ".go") == filepath.Base(filepath.Dir(p)) {
7270
files = append(files, p)
7371
fmt.Printf("Successfully bisynced %s\n", logging.Bold(p))
@@ -83,7 +81,7 @@ func BisyncGo(ctx context.Context, outputPath, lekkoPath, repoPath string) ([]st
8381
return nil, errors.Wrap(err, "sync")
8482
}
8583
if err := WriteContentsToLocalRepo(ctx, repoContents, repoPath); err != nil {
86-
return nil, err
84+
return nil, errors.Wrap(err, "write to repository")
8785
}
8886
generator, err := gen.NewGoGenerator(mf.Module.Mod.Path, outputPath, lekkoPath, repoContents)
8987
if err != nil {
@@ -324,7 +322,6 @@ func (g *goSyncer) AstToNamespace(pf *ast.File, fds *descriptorpb.FileDescriptor
324322

325323
// Translate a collection of Go files to a representation of repository contents.
326324
// Files -> repo instead of file -> namespace because FDS is shared repo-wide.
327-
// Takes file paths instead of contents for more helpful error reporting.
328325
func (g *goSyncer) Sync(filePaths ...string) (*featurev1beta1.RepositoryContents, error) {
329326
ret := &featurev1beta1.RepositoryContents{FileDescriptorSet: protoutils.NewDefaultFileDescriptorSet()}
330327
for _, filePath := range filePaths {
@@ -342,6 +339,23 @@ func (g *goSyncer) Sync(filePaths ...string) (*featurev1beta1.RepositoryContents
342339
return ret, nil
343340
}
344341

342+
func (g *goSyncer) SyncContents(contentMap map[string]string) (*featurev1beta1.RepositoryContents, error) {
343+
ret := &featurev1beta1.RepositoryContents{FileDescriptorSet: protoutils.NewDefaultFileDescriptorSet()}
344+
for namespace, contents := range contentMap {
345+
astf, err := parser.ParseFile(g.fset, namespace, contents, parser.ParseComments|parser.AllErrors|parser.SkipObjectResolution)
346+
if err != nil {
347+
return nil, errors.Wrapf(err, "parse %s", namespace)
348+
}
349+
ns, err := g.AstToNamespace(astf, ret.FileDescriptorSet)
350+
if err != nil {
351+
return nil, errors.Wrapf(err, "translate %s", namespace)
352+
}
353+
ret.Namespaces = append(ret.Namespaces, ns)
354+
}
355+
356+
return ret, nil
357+
}
358+
345359
// TODO - is this only used for context keys, or other things?
346360
func (g *goSyncer) exprToValue(expr ast.Expr) string {
347361
//fmt.Printf("%+v\n", expr)

pkg/sync/push.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ func Push(ctx context.Context, commitMessage string, force bool, dot *dotlekko.D
115115
}))
116116
switch nlProject.Language {
117117
case native.LangTypeScript:
118-
err = BisyncTS(lekkoPath, repoPath)
118+
err = BisyncTS(ctx, lekkoPath, repoPath)
119119
if err != nil {
120120
return err
121121
}

pkg/sync/repo.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ import (
3434
"google.golang.org/protobuf/types/known/anypb"
3535
)
3636

37+
// Writes contents to a local repository at the specified path.
38+
// Conflicting namespaces are essentially completely overwritten.
39+
// Namespaces in the local repository but not in the passed contents are untouched.
40+
// New namespaces are created.
3741
func WriteContentsToLocalRepo(ctx context.Context, contents *featurev1beta1.RepositoryContents, repoPath string) error {
3842
// NOTE: For now, this function still needs a proper Lekko repository as a prereq,
3943
// because it's uncertain if we'll ever need functionality to create a local repository

pkg/sync/ts.go

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,84 @@
1515
package sync
1616

1717
import (
18-
"fmt"
18+
"context"
19+
"encoding/base64"
1920
"os/exec"
21+
"path/filepath"
2022
"strings"
2123

24+
"github.com/lekkodev/cli/pkg/gen"
25+
"github.com/lekkodev/cli/pkg/native"
26+
protoutils "github.com/lekkodev/cli/pkg/proto"
2227
"github.com/pkg/errors"
28+
"google.golang.org/protobuf/proto"
29+
30+
featurev1beta1 "buf.build/gen/go/lekkodev/cli/protocolbuffers/go/lekko/feature/v1beta1"
2331
)
2432

25-
func BisyncTS(lekkoPath, repoPath string) error {
26-
tsSyncCmd := exec.Command("npx", "lekko-repo-sync", "--lekko-dir", lekkoPath, "--repo-path", repoPath)
33+
// TODO: consider consolidating bisync methods
34+
func BisyncTS(ctx context.Context, lekkoPath, repoPath string) error {
35+
repoContents, err := SyncTS(lekkoPath)
36+
if err != nil {
37+
return errors.Wrap(err, "sync")
38+
}
39+
if err := WriteContentsToLocalRepo(ctx, repoContents, repoPath); err != nil {
40+
return errors.Wrap(err, "write to repository")
41+
}
42+
// Generate only for namespaces that were synced
43+
for _, namespace := range repoContents.Namespaces {
44+
outFilename := filepath.Join(lekkoPath, namespace.Name+native.LangTypeScript.Ext())
45+
if err := gen.GenFormattedTS(ctx, repoPath, namespace.Name, outFilename); err != nil {
46+
return errors.Wrapf(err, "generate code for %s", namespace.Name)
47+
}
48+
}
49+
return nil
50+
}
51+
52+
func SyncTS(lekkoPath string) (*featurev1beta1.RepositoryContents, error) {
53+
// TODO: If command not found, give helpful error message prompting user to update dependencies
54+
tsSyncCmd := exec.Command("npx", "lekko-sync", "--lekko-dir", lekkoPath)
2755
output, err := tsSyncCmd.CombinedOutput()
28-
outputStr := strings.TrimSpace(string(output))
29-
if len(outputStr) > 0 {
30-
fmt.Println(outputStr)
56+
if err != nil {
57+
return nil, errors.Wrapf(err, "sync ts: %s", output)
3158
}
59+
return syncTSOutputToRepositoryContents(output)
60+
}
61+
62+
func SyncTSFiles(lekkoFiles ...string) (*featurev1beta1.RepositoryContents, error) {
63+
commafied := strings.Join(lekkoFiles, ",")
64+
tsSyncCmd := exec.Command("npx", "lekko-sync", "--lekko-files", commafied)
65+
output, err := tsSyncCmd.CombinedOutput()
3266
if err != nil {
33-
return errors.Wrap(err, "bisync ts")
67+
return nil, errors.Wrapf(err, "sync ts: %s", output)
3468
}
35-
return nil
69+
return syncTSOutputToRepositoryContents(output)
70+
}
71+
72+
// Output is expected to be base64 encoded serialized RepositoryContents message
73+
func syncTSOutputToRepositoryContents(output []byte) (*featurev1beta1.RepositoryContents, error) {
74+
// Because Protobuf is not self-describing, we have to jump through some hoops here for deserialization.
75+
// The RepositoryContents message contains the FDS which we want to use as the resolver for unmarshalling
76+
// the rest of the contents.
77+
decoded, err := base64.StdEncoding.DecodeString(string(output))
78+
if err != nil {
79+
return nil, errors.Wrap(err, "decode sync ts output")
80+
}
81+
// First pass unmarshal to get the FDS while ignoring any unresolvable Anys
82+
tempRepoContents := &featurev1beta1.RepositoryContents{}
83+
err = proto.UnmarshalOptions{DiscardUnknown: true, Resolver: &protoutils.IgnoreAnyResolver{}}.Unmarshal(decoded, tempRepoContents)
84+
if err != nil {
85+
return nil, errors.Wrap(err, "initial unmarshal sync ts output")
86+
}
87+
typeRegistry, err := protoutils.FileDescriptorSetToTypeRegistry(tempRepoContents.FileDescriptorSet)
88+
if err != nil {
89+
return nil, errors.Wrap(err, "get type registry")
90+
}
91+
// Re-unmarshal using type registry this time - we can resolve Anys correctly
92+
repoContents := &featurev1beta1.RepositoryContents{}
93+
err = proto.UnmarshalOptions{Resolver: typeRegistry}.Unmarshal(decoded, repoContents)
94+
if err != nil {
95+
return nil, errors.Wrap(err, "final unmarshal sync ts output")
96+
}
97+
return repoContents, nil
3698
}

0 commit comments

Comments
 (0)