Skip to content

Commit 9cd6119

Browse files
authored
fix: fix chain scaffolding checks (#4886)
* fix: Scaffolding a type after a message throws an error * Fix the same type that can be scaffolded twice * add changelog * fix plugin tests
1 parent b1b9ef0 commit 9cd6119

7 files changed

Lines changed: 118 additions & 2 deletions

File tree

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
### Fixes
1717

18+
- [#4886](https://github.com/ignite/cli/pull/4886) Fix chain scaffolding checks.
1819
- [#4889](https://github.com/ignite/cli/pull/4889) Plugin data race.
1920

2021
## [`v29.8.0`](https://github.com/ignite/cli/releases/tag/v29.8.0)

ignite/services/plugin/plugin.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,5 +425,8 @@ func (p *Plugin) outdatedBinary() bool {
425425
fmt.Printf("error while walking app source path %q\n", p.srcPath)
426426
return false
427427
}
428-
return mostRecent.After(binaryTime)
428+
// Rebuild when source files are newer OR have the same timestamp as the binary.
429+
// In some environments (such as fresh CI checkouts), mtimes can be normalized
430+
// to identical values, and strict "after" checks may incorrectly reuse stale binaries.
431+
return !mostRecent.Before(binaryTime)
429432
}

ignite/services/plugin/plugin_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,28 @@ func TestPluginClean(t *testing.T) {
541541
}
542542
}
543543

544+
func TestPluginOutdatedBinary(t *testing.T) {
545+
t.Run("returns true when source and binary mtimes are equal", func(t *testing.T) {
546+
tmp := t.TempDir()
547+
srcFile := filepath.Join(tmp, "main.go")
548+
binFile := filepath.Join(tmp, "app.ign")
549+
550+
require.NoError(t, os.WriteFile(srcFile, []byte("package main\n"), 0o644))
551+
require.NoError(t, os.WriteFile(binFile, []byte("binary"), 0o755))
552+
553+
equalTime := time.Now().Add(-time.Minute).Truncate(time.Second)
554+
require.NoError(t, os.Chtimes(srcFile, equalTime, equalTime))
555+
require.NoError(t, os.Chtimes(binFile, equalTime, equalTime))
556+
557+
p := Plugin{
558+
srcPath: tmp,
559+
name: "app",
560+
}
561+
562+
require.True(t, p.outdatedBinary())
563+
})
564+
}
565+
544566
// scaffoldPlugin runs Scaffold and updates the go.mod so it uses the
545567
// current ignite/cli sources.
546568
func scaffoldPlugin(t *testing.T, dir, name string, sharedHost bool) string {

ignite/services/scaffolder/component.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,35 @@ func checkComponentCreated(appPath, moduleName string, compName multiformatname.
106106
return err
107107
}
108108

109+
// checkTypeProtoCreated checks if the proto type already exists in the module proto package.
110+
func checkTypeProtoCreated(
111+
ctx context.Context,
112+
appPath, appName, protoDir, moduleName string,
113+
compName multiformatname.Name,
114+
) error {
115+
path := filepath.Join(appPath, protoDir, appName, moduleName)
116+
pkgs, err := protoanalysis.Parse(ctx, protoanalysis.NewCache(), path)
117+
if err != nil {
118+
return err
119+
}
120+
121+
for _, pkg := range pkgs {
122+
for _, msg := range pkg.Messages {
123+
if !strings.EqualFold(msg.Name, compName.PascalCase) {
124+
continue
125+
}
126+
127+
return errors.Errorf("component %s with name %s is already created (type %s exists)",
128+
componentType,
129+
compName.Original,
130+
msg.Name,
131+
)
132+
}
133+
}
134+
135+
return nil
136+
}
137+
109138
// checkCustomTypes returns error if one of the types is invalid.
110139
func checkCustomTypes(ctx context.Context, appPath, appName, protoDir, module string, fields []string) error {
111140
path := filepath.Join(appPath, protoDir, appName, module)

ignite/services/scaffolder/component_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package scaffolder
22

33
import (
4+
"context"
5+
"os"
6+
"path/filepath"
47
"testing"
58

69
"github.com/stretchr/testify/require"
@@ -105,3 +108,42 @@ func TestContainsCustomTypes(t *testing.T) {
105108
})
106109
}
107110
}
111+
112+
func TestCheckTypeProtoCreated(t *testing.T) {
113+
t.Run("should fail when proto type already exists", func(t *testing.T) {
114+
tmp := t.TempDir()
115+
protoFile := filepath.Join(tmp, "proto", "blog", "blog", "v1", "post.proto")
116+
require.NoError(t, os.MkdirAll(filepath.Dir(protoFile), 0o755))
117+
118+
content := `syntax = "proto3";
119+
package blog.blog.v1;
120+
121+
message Post {}
122+
`
123+
require.NoError(t, os.WriteFile(protoFile, []byte(content), 0o644))
124+
125+
name, err := multiformatname.NewName("post")
126+
require.NoError(t, err)
127+
128+
err = checkTypeProtoCreated(context.Background(), tmp, "blog", "proto", "blog", name)
129+
require.EqualError(t, err, "component type with name post is already created (type Post exists)")
130+
})
131+
132+
t.Run("should pass when proto type does not exist", func(t *testing.T) {
133+
tmp := t.TempDir()
134+
protoFile := filepath.Join(tmp, "proto", "blog", "blog", "v1", "comment.proto")
135+
require.NoError(t, os.MkdirAll(filepath.Dir(protoFile), 0o755))
136+
137+
content := `syntax = "proto3";
138+
package blog.blog.v1;
139+
140+
message Comment {}
141+
`
142+
require.NoError(t, os.WriteFile(protoFile, []byte(content), 0o644))
143+
144+
name, err := multiformatname.NewName("post")
145+
require.NoError(t, err)
146+
147+
require.NoError(t, checkTypeProtoCreated(context.Background(), tmp, "blog", "proto", "blog", name))
148+
})
149+
}

ignite/services/scaffolder/type.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,11 @@ func SingletonType() AddTypeKind {
7272

7373
// DryType only creates a type with a basic definition.
7474
func DryType() AddTypeKind {
75-
return func(*addTypeOptions) {}
75+
return func(o *addTypeOptions) {
76+
// Dry type scaffolding only adds a proto type definition and never generates CRUD messages.
77+
// Force this option so component validity checks don't treat existing Msg* types as conflicts.
78+
o.withoutMessage = true
79+
}
7680
}
7781

7882
// TypeWithModule module to scaffold type into.
@@ -140,6 +144,9 @@ func (s Scaffolder) AddType(
140144
if err := checkComponentValidity(s.appPath, moduleName, name, o.withoutMessage); err != nil {
141145
return err
142146
}
147+
if err := checkTypeProtoCreated(ctx, s.appPath, s.modpath.Package, s.protoDir, moduleName, name); err != nil {
148+
return err
149+
}
143150

144151
// Check and parse provided fields
145152
if err := checkCustomTypes(ctx, s.appPath, s.modpath.Package, s.protoDir, moduleName, o.fields); err != nil {

ignite/services/scaffolder/type_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,18 @@ func TestParseTypeFields(t *testing.T) {
131131
},
132132
},
133133
},
134+
{
135+
name: "dry type defaults to no message",
136+
addKind: DryType(),
137+
addOptions: []AddTypeOption{},
138+
expectedOptions: addTypeOptions{
139+
moduleName: testModuleName,
140+
withoutMessage: true,
141+
signer: testSigner,
142+
},
143+
shouldError: false,
144+
expectedFields: nil,
145+
},
134146
}
135147

136148
for _, tc := range tests {

0 commit comments

Comments
 (0)