@@ -76,62 +76,18 @@ export async function runTests(options: RunOptions = {}) {
7676 const results = await runner . runTests ( testCases ) ;
7777 spinner . stop ( ) ;
7878
79+ // Save to database
7980 // Save to database
8081 const db = new TestDatabase ( ) ;
8182 db . saveRun ( results ) ;
82- db . close ( ) ;
83+
84+ // Calculate results for cloud upload (and for sync logic)
85+ const currentRunId = results . id ; // Assuming results has ID
8386
8487 // Report results
8588 const reporter = new TestReporter ( ) ;
8689 reporter . printResults ( results , config . outputFormat ) ;
8790
88- // Calculate results for cloud upload
89- const testResults = results . results . map ( ( result : TestResult ) => {
90- // Map from internal TestResult to cloud service TestResult
91- const mappedResult : import ( '../services/cloud.service' ) . CloudTestResult = {
92- test_name : result . testCase . description ,
93- test_description : result . testCase . description ,
94- prompt : typeof result . testCase . prompt === 'string'
95- ? result . testCase . prompt
96- : JSON . stringify ( result . testCase . prompt ) ,
97- input_data : result . testCase . variables ,
98- expected_output : result . expectedOutput ,
99- actual_output : result . actualOutput ,
100- score : result . score ,
101- method : result . testCase . config ?. method || 'exact' ,
102- status : result . status ,
103- model : result . metadata . provider || '' ,
104- tokens_used : result . metadata . tokens ,
105- latency_ms : result . metadata . duration ,
106- cost_usd : result . metadata . cost ,
107- error_message : result . error ,
108- error_type : undefined , // No error type in current TestResult interface
109- } ;
110- return mappedResult ;
111- } ) ;
112-
113- // Calculate total cost from all test results
114- const totalCost = results . results . reduce ( ( sum , result ) => {
115- return sum + ( result . metadata . cost || 0 ) ;
116- } , 0 ) ;
117-
118- const resultsSummary = {
119- totalTests : results . results . length ,
120- passedTests : results . passed ,
121- failedTests : results . failed ,
122- durationMs : Date . now ( ) - startTime ,
123- totalCost : totalCost || 0.05 , // fallback value
124- tests : testResults ,
125- } ;
126-
127- // Print results to console (existing logic)
128- console . log ( chalk . green ( `\n✅ ${ resultsSummary . passedTests } passed` ) ) ;
129- console . log ( chalk . red ( `❌ ${ resultsSummary . failedTests } failed\n` ) ) ;
130-
131- // Show upsell hint if tests failed
132- displayRunSummary ( results . results ) ;
133-
134- // NEW: Cloud upload logic
13591 const isCI = options . ci ||
13692 process . env . CI === 'true' ||
13793 ! ! process . env . GITHUB_ACTIONS ||
@@ -140,11 +96,13 @@ export async function runTests(options: RunOptions = {}) {
14096 const shouldUpload = options . cloud || isCI ;
14197
14298 if ( shouldUpload ) {
143- await uploadToCloud ( resultsSummary , options ) ;
99+ await syncPendingRuns ( db , options ) ;
144100 }
145101
102+ db . close ( ) ;
103+
146104 // Exit with error code if tests failed
147- if ( resultsSummary . failedTests > 0 ) {
105+ if ( results . failed > 0 ) {
148106 process . exit ( 1 ) ;
149107 }
150108
@@ -163,86 +121,90 @@ export const runCommand = new Command('run')
163121 await runTests ( options ) ;
164122 } ) ;
165123
166- async function uploadToCloud ( results : {
167- totalTests : number ;
168- passedTests : number ;
169- failedTests : number ;
170- durationMs : number ;
171- totalCost : number ;
172- tests : import ( '../services/cloud.service' ) . CloudTestResult [ ] ;
173- } , options : any ) {
124+ async function syncPendingRuns ( db : TestDatabase , options : any ) {
125+ const pendingRuns = db . getPendingUploads ( ) ;
126+
127+ if ( pendingRuns . length === 0 ) return ;
128+
129+ console . log ( chalk . blue ( `\n☁️ Syncing ${ pendingRuns . length } pending run(s) to Cloud...` ) ) ;
130+
174131 const cloudService = new CloudService ( ) ;
175132 await cloudService . init ( ) ;
176133
177- const isAuth = await cloudService . isAuthenticated ( ) ;
178-
179- if ( ! isAuth ) {
180- console . log ( chalk . yellow ( '\n⚠️ Not authenticated with Cloud.' ) ) ;
181- console . log ( chalk . gray ( 'Results saved locally. Run `tuneprompt activate` to enable cloud sync\n' ) ) ;
134+ if ( ! ( await cloudService . isAuthenticated ( ) ) ) {
135+ console . log ( chalk . yellow ( '⚠️ Not authenticated. Run `tuneprompt activate` first.' ) ) ;
182136 return ;
183137 }
184138
185- // Get or create project
139+ // Get project ID once
186140 let projectId : string ;
187141 try {
188142 const projects = await cloudService . getProjects ( ) ;
189143 if ( projects . length === 0 ) {
190- console . log ( chalk . blue ( '📁 Creating default project...' ) ) ;
191144 const project = await cloudService . createProject ( 'Default Project' ) ;
192145 projectId = project . id ;
193- console . log ( chalk . green ( `✅ Project created: ${ projectId } ` ) ) ;
194146 } else {
195- projectId = projects [ 0 ] . id ; // Use first project
196- console . log ( chalk . gray ( `📋 Using existing project: ${ projectId } ` ) ) ;
147+ projectId = projects [ 0 ] . id ;
197148 }
198- } catch ( error ) {
199- console . log ( chalk . yellow ( '⚠️ Failed to get project' ) , error ) ;
149+ } catch ( err ) {
150+ console . log ( chalk . yellow ( '⚠️ Failed to get project info' ) ) ;
200151 return ;
201152 }
202153
203- // Get Git context
154+ // Common Git/Env context
204155 let gitContext = { } ;
205156 try {
206157 gitContext = {
207158 commit_hash : execSync ( 'git rev-parse HEAD' , { encoding : 'utf-8' } ) . trim ( ) ,
208159 branch_name : execSync ( 'git rev-parse --abbrev-ref HEAD' , { encoding : 'utf-8' } ) . trim ( ) ,
209160 commit_message : execSync ( 'git log -1 --pretty=%B' , { encoding : 'utf-8' } ) . trim ( ) ,
210161 } ;
211- } catch {
212- // Not a git repo
213- }
162+ } catch { }
214163
215- // Detect CI provider
216164 let ciProvider ;
217165 if ( process . env . GITHUB_ACTIONS ) ciProvider = 'github' ;
218166 else if ( process . env . GITLAB_CI ) ciProvider = 'gitlab' ;
219167 else if ( process . env . JENKINS_HOME ) ciProvider = 'jenkins' ;
220168 else if ( process . env . CIRCLECI ) ciProvider = 'circleci' ;
221169
222- // Prepare run data
223- const runData : import ( '../services/cloud.service' ) . RunData = {
224- project_id : projectId ,
225- environment : options . ci || process . env . CI ? 'ci' : 'local' ,
226- ci_provider : ciProvider ,
227- total_tests : results . totalTests ,
228- passed_tests : results . passedTests ,
229- failed_tests : results . failedTests ,
230- duration_ms : results . durationMs ,
231- cost_usd : results . totalCost ,
232- started_at : new Date ( Date . now ( ) - results . durationMs ) . toISOString ( ) ,
233- completed_at : new Date ( ) . toISOString ( ) ,
234- test_results : results . tests ,
235- ...gitContext ,
236- } ;
237-
238- console . log ( chalk . blue ( '\n☁️ Uploading results to Cloud...' ) ) ;
239-
240- const uploadResult = await cloudService . uploadRun ( runData ) ;
241-
242- if ( uploadResult . success ) {
243- console . log ( chalk . green ( '✅ Results uploaded successfully' ) ) ;
244- console . log ( chalk . gray ( `View at: ${ uploadResult . url } \n` ) ) ;
245- } else {
246- console . log ( chalk . yellow ( '⚠️ Failed to upload results:' ) , uploadResult . error ) ;
170+ // Upload each run
171+ for ( const run of pendingRuns ) {
172+ const runData : import ( '../services/cloud.service' ) . RunData = {
173+ project_id : projectId ,
174+ environment : options . ci ? 'ci' : 'local' ,
175+ ci_provider : ciProvider ,
176+ total_tests : run . totalTests ,
177+ passed_tests : run . passed ,
178+ failed_tests : run . failed ,
179+ duration_ms : run . duration ,
180+ cost_usd : run . results . reduce ( ( sum , r ) => sum + ( r . metadata . cost || 0 ) , 0 ) || 0.05 , // fallback
181+ started_at : new Date ( run . timestamp . getTime ( ) - run . duration ) . toISOString ( ) ,
182+ completed_at : run . timestamp . toISOString ( ) ,
183+ test_results : run . results . map ( r => ( {
184+ test_name : r . testCase . description ,
185+ test_description : r . testCase . description ,
186+ prompt : typeof r . testCase . prompt === 'string' ? r . testCase . prompt : JSON . stringify ( r . testCase . prompt ) ,
187+ input_data : r . testCase . variables ,
188+ expected_output : r . expectedOutput ,
189+ actual_output : r . actualOutput ,
190+ score : r . score ,
191+ method : r . testCase . config ?. method || 'exact' ,
192+ status : r . status ,
193+ model : r . metadata . provider || '' ,
194+ tokens_used : r . metadata . tokens ,
195+ latency_ms : r . metadata . duration ,
196+ cost_usd : r . metadata . cost ,
197+ } ) ) ,
198+ ...gitContext // Applying current git context to old runs slightly inaccurate but acceptable
199+ } ;
200+
201+ const uploadResult = await cloudService . uploadRun ( runData ) ;
202+
203+ if ( uploadResult . success ) {
204+ db . markAsUploaded ( run . id ) ;
205+ console . log ( chalk . green ( ` ✓ Uploaded run from ${ run . timestamp . toLocaleTimeString ( ) } ` ) ) ;
206+ } else {
207+ console . log ( chalk . red ( ` ✗ Failed to upload run ${ run . id } : ${ uploadResult . error } ` ) ) ;
208+ }
247209 }
248210}
0 commit comments