@@ -57,80 +57,72 @@ func (vr *ValidateResult) String() string {
5757// Validation defines the interface for project validation strategies.
5858type 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.
194174type 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-
213184func (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 }
0 commit comments