Skip to content

Commit 3735bd2

Browse files
authored
MCP: Streamline validation for node.js templates (#3985)
## Changes Validation for 'overfit' for the tRPC template. I generalized it to follow node.js standards that all node.js templates can implement. Before: * Build * npm run build * Typecheck * cd client && npx tsc --noEmit * Test * npm test After: * Build * npm run build –if-present * Typecheck * npm run typecheck –if-present * Test * npm run test –if-present ## Why <!-- Why are these changes needed? Provide the context that the reviewer might be missing. For example, were there any decisions behind the change that are not reflected in the code itself? --> ## Tests <!-- How have you tested the changes? --> <!-- If your PR needs to be included in the release notes for next release, add a separate entry in NEXT_CHANGELOG.md as part of your PR. -->
1 parent 82a76d3 commit 3735bd2

File tree

4 files changed

+79
-101
lines changed

4 files changed

+79
-101
lines changed

experimental/apps-mcp/lib/providers/io/validate.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@ func (p *Provider) Validate(ctx context.Context, args *ValidateArgs) (*ValidateR
4343
valConfig := p.config.Validation
4444
if valConfig.Command != "" {
4545
log.Infof(ctx, "using custom validation command: command=%s", valConfig.Command)
46-
validation = NewValidationCmd(valConfig.Command, "")
46+
validation = NewValidationCmd(valConfig.Command)
4747
}
4848
}
4949

5050
if validation == nil {
51-
log.Info(ctx, "using default tRPC validation strategy")
52-
validation = NewValidationTRPC()
51+
log.Info(ctx, "using default Node.js validation strategy")
52+
validation = NewValidationNodeJs()
5353
}
5454

5555
result, err := validation.Validate(ctx, workDir)

experimental/apps-mcp/lib/providers/io/validation.go

Lines changed: 72 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -57,80 +57,72 @@ func (vr *ValidateResult) String() string {
5757
// Validation defines the interface for project validation strategies.
5858
type Validation interface {
5959
Validate(ctx context.Context, workDir string) (*ValidateResult, error)
60-
DockerImage() string
6160
}
6261

63-
// ValidationTRPC implements validation for tRPC-based projects using build, type check, and tests.
64-
type ValidationTRPC struct{}
62+
// ValidationNodeJs implements validation for Node.js-based projects using build, type check, and tests.
63+
type ValidationNodeJs struct{}
6564

66-
func NewValidationTRPC() Validation {
67-
return &ValidationTRPC{}
65+
func NewValidationNodeJs() Validation {
66+
return &ValidationNodeJs{}
6867
}
6968

70-
func (v *ValidationTRPC) DockerImage() string {
71-
return "node:20-alpine3.22"
69+
type validationStep struct {
70+
name string
71+
command string
72+
errorPrefix string
73+
displayName string
7274
}
7375

74-
func (v *ValidationTRPC) Validate(ctx context.Context, workDir string) (*ValidateResult, error) {
75-
log.Info(ctx, "starting tRPC validation (build + type check + tests)")
76+
func (v *ValidationNodeJs) Validate(ctx context.Context, workDir string) (*ValidateResult, error) {
77+
log.Info(ctx, "Starting Node.js validation: build + typecheck + tests")
7678
startTime := time.Now()
7779
var progressLog []string
7880

79-
progressLog = append(progressLog, "🔄 Starting validation: build + type check + tests")
80-
81-
log.Info(ctx, "step 1/3: running build...")
82-
progressLog = append(progressLog, "⏳ Step 1/3: Running build...")
83-
buildStart := time.Now()
84-
if err := v.runBuild(ctx, workDir); err != nil {
85-
buildDuration := time.Since(buildStart)
86-
log.Errorf(ctx, "build failed (duration: %.1fs)", buildDuration.Seconds())
87-
progressLog = append(progressLog, fmt.Sprintf("❌ Build failed (%.1fs)", buildDuration.Seconds()))
88-
return &ValidateResult{
89-
Success: false,
90-
Message: "Build failed",
91-
Details: err,
92-
ProgressLog: progressLog,
93-
}, nil
94-
}
95-
buildDuration := time.Since(buildStart)
96-
log.Infof(ctx, "✓ build passed: duration=%.1fs", buildDuration.Seconds())
97-
progressLog = append(progressLog, fmt.Sprintf("✅ Build passed (%.1fs)", buildDuration.Seconds()))
98-
99-
log.Info(ctx, "step 2/3: running type check...")
100-
progressLog = append(progressLog, "⏳ Step 2/3: Running type check...")
101-
typeCheckStart := time.Now()
102-
if err := v.runClientTypeCheck(ctx, workDir); err != nil {
103-
typeCheckDuration := time.Since(typeCheckStart)
104-
log.Errorf(ctx, "type check failed (duration: %.1fs)", typeCheckDuration.Seconds())
105-
progressLog = append(progressLog, fmt.Sprintf("❌ Type check failed (%.1fs)", typeCheckDuration.Seconds()))
106-
return &ValidateResult{
107-
Success: false,
108-
Message: "Type check failed",
109-
Details: err,
110-
ProgressLog: progressLog,
111-
}, nil
81+
progressLog = append(progressLog, "🔄 Starting Node.js validation: build + typecheck + tests")
82+
83+
steps := []validationStep{
84+
{
85+
name: "build",
86+
command: "npm run build --if-present",
87+
errorPrefix: "Failed to run npm build",
88+
displayName: "Build",
89+
},
90+
{
91+
name: "typecheck",
92+
command: "npm run typecheck --if-present",
93+
errorPrefix: "Failed to run client typecheck",
94+
displayName: "Type check",
95+
},
96+
{
97+
name: "tests",
98+
command: "npm run test --if-present",
99+
errorPrefix: "Failed to run tests",
100+
displayName: "Tests",
101+
},
112102
}
113-
typeCheckDuration := time.Since(typeCheckStart)
114-
log.Infof(ctx, "✓ type check passed: duration=%.1fs", typeCheckDuration.Seconds())
115-
progressLog = append(progressLog, fmt.Sprintf("✅ Type check passed (%.1fs)", typeCheckDuration.Seconds()))
116-
117-
log.Info(ctx, "step 3/3: running tests...")
118-
progressLog = append(progressLog, "⏳ Step 3/3: Running tests...")
119-
testStart := time.Now()
120-
if err := v.runTests(ctx, workDir); err != nil {
121-
testDuration := time.Since(testStart)
122-
log.Errorf(ctx, "tests failed (duration: %.1fs)", testDuration.Seconds())
123-
progressLog = append(progressLog, fmt.Sprintf("❌ Tests failed (%.1fs)", testDuration.Seconds()))
124-
return &ValidateResult{
125-
Success: false,
126-
Message: "Tests failed",
127-
Details: err,
128-
ProgressLog: progressLog,
129-
}, nil
103+
104+
for i, step := range steps {
105+
stepNum := fmt.Sprintf("%d/%d", i+1, len(steps))
106+
log.Infof(ctx, "step %s: running %s...", stepNum, step.name)
107+
progressLog = append(progressLog, fmt.Sprintf("⏳ Step %s: Running %s...", stepNum, step.displayName))
108+
109+
stepStart := time.Now()
110+
err := runCommand(ctx, workDir, step.command)
111+
if err != nil {
112+
stepDuration := time.Since(stepStart)
113+
log.Errorf(ctx, "%s failed (duration: %.1fs)", step.name, stepDuration.Seconds())
114+
progressLog = append(progressLog, fmt.Sprintf("❌ %s failed (%.1fs)", step.displayName, stepDuration.Seconds()))
115+
return &ValidateResult{
116+
Success: false,
117+
Message: step.errorPrefix,
118+
Details: err,
119+
ProgressLog: progressLog,
120+
}, nil
121+
}
122+
stepDuration := time.Since(stepStart)
123+
log.Infof(ctx, "✓ %s passed: duration=%.1fs", step.name, stepDuration.Seconds())
124+
progressLog = append(progressLog, fmt.Sprintf("✅ %s passed (%.1fs)", step.displayName, stepDuration.Seconds()))
130125
}
131-
testDuration := time.Since(testStart)
132-
log.Infof(ctx, "✓ tests passed: duration=%.1fs", testDuration.Seconds())
133-
progressLog = append(progressLog, fmt.Sprintf("✅ Tests passed (%.1fs)", testDuration.Seconds()))
134126

135127
totalDuration := time.Since(startTime)
136128
log.Infof(ctx, "✓ all validation checks passed: total_duration=%.1fs, steps=%s",
@@ -139,27 +131,15 @@ func (v *ValidationTRPC) Validate(ctx context.Context, workDir string) (*Validat
139131

140132
return &ValidateResult{
141133
Success: true,
142-
Message: "All validation checks passed (build + type check + tests)",
134+
Message: "All validation checks passed",
143135
ProgressLog: progressLog,
144136
}, nil
145137
}
146138

147-
func (v *ValidationTRPC) runBuild(ctx context.Context, workDir string) *ValidationDetail {
148-
return runCommand(ctx, workDir, "npm run build")
149-
}
150-
151-
func (v *ValidationTRPC) runClientTypeCheck(ctx context.Context, workDir string) *ValidationDetail {
152-
return runCommand(ctx, workDir, "cd client && npx tsc --noEmit")
153-
}
154-
155-
func (v *ValidationTRPC) runTests(ctx context.Context, workDir string) *ValidationDetail {
156-
return runCommand(ctx, workDir, "npm test")
157-
}
158-
159139
// runCommand executes a shell command in the specified directory
160-
func runCommand(ctx context.Context, dir, command string) *ValidationDetail {
140+
func runCommand(ctx context.Context, workDir, command string) *ValidationDetail {
161141
cmd := exec.CommandContext(ctx, "sh", "-c", command)
162-
cmd.Dir = dir
142+
cmd.Dir = workDir
163143

164144
var stdout, stderr bytes.Buffer
165145
cmd.Stdout = &stdout
@@ -192,40 +172,36 @@ func runCommand(ctx context.Context, dir, command string) *ValidationDetail {
192172

193173
// ValidationCmd implements validation using a custom command specified by the user.
194174
type ValidationCmd struct {
195-
Command string
196-
DockerImg string
175+
Command string
197176
}
198177

199-
func NewValidationCmd(command, dockerImage string) Validation {
200-
if dockerImage == "" {
201-
dockerImage = "node:20-alpine3.22"
202-
}
178+
func NewValidationCmd(command string) Validation {
203179
return &ValidationCmd{
204-
Command: command,
205-
DockerImg: dockerImage,
180+
Command: command,
206181
}
207182
}
208183

209-
func (v *ValidationCmd) DockerImage() string {
210-
return v.DockerImg
211-
}
212-
213184
func (v *ValidationCmd) Validate(ctx context.Context, workDir string) (*ValidateResult, error) {
214185
log.Infof(ctx, "starting custom validation: command=%s", v.Command)
215186
startTime := time.Now()
216187
var progressLog []string
217188

218189
progressLog = append(progressLog, "🔄 Starting custom validation: "+v.Command)
219190

220-
detail := runCommand(ctx, workDir, v.Command)
221-
if detail != nil {
191+
fullCommand := v.Command
192+
err := runCommand(ctx, workDir, fullCommand)
193+
if err != nil {
222194
duration := time.Since(startTime)
223-
log.Errorf(ctx, "custom validation failed (duration: %.1fs, exit_code: %d)", duration.Seconds(), detail.ExitCode)
224-
progressLog = append(progressLog, fmt.Sprintf("❌ Validation failed (%.1fs) - exit code: %d", duration.Seconds(), detail.ExitCode))
195+
log.Errorf(ctx, "custom validation command failed (duration: %.1fs, error: %v)", duration.Seconds(), err)
196+
progressLog = append(progressLog, fmt.Sprintf("❌ Command failed (%.1fs): %v", duration.Seconds(), err))
225197
return &ValidateResult{
226-
Success: false,
227-
Message: "Custom validation command failed",
228-
Details: detail,
198+
Success: false,
199+
Message: "Custom validation command failed",
200+
Details: &ValidationDetail{
201+
ExitCode: -1,
202+
Stdout: "",
203+
Stderr: fmt.Sprintf("Failed to run validation command: %v", err),
204+
},
229205
ProgressLog: progressLog,
230206
}, nil
231207
}

experimental/apps-mcp/lib/templates/trpc/client/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
"type": "module",
66
"scripts": {
77
"dev": "vite --host",
8-
"build": "tsc -b && vite build -l info",
8+
"build": "tsc -p tsconfig.json -b && vite build -l info",
99
"lint": "eslint .",
10+
"check": "tsc --noEmit",
1011
"preview": "vite preview"
1112
},
1213
"dependencies": {

experimental/apps-mcp/lib/templates/trpc/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"private": true,
44
"scripts": {
55
"install:all": "cd server && npm install && cd ../client && npm install",
6-
"build": "npm run install:all && cd server && npm run check && cd .. && cd client && npm run build && cd .. && rm -rf server/public && mv client/dist server/public",
6+
"build": "npm run install:all && cd server && cd ../client && npm run build && cd .. && rm -rf server/public && mv client/dist server/public",
7+
"typecheck": "cd server && npm run check && cd ../client && npm run check",
78
"start": "cd server && npm start",
89
"test": "cd server && npm test",
910
"clean": "rm -rf server/public server/node_modules server/dist client/node_modules"

0 commit comments

Comments
 (0)