| 
 | 1 | +#!/usr/bin/env node  | 
 | 2 | + | 
 | 3 | +import "dotenv/config";  | 
 | 4 | +import fs from "fs/promises";  | 
 | 5 | +import path from "path";  | 
 | 6 | +import { diff } from "json-diff";  | 
 | 7 | + | 
 | 8 | +const GREEN_CHECK = "\x1b[32m✔\x1b[0m";  | 
 | 9 | +const RED_CROSS = "\x1b[31m✖\x1b[0m";  | 
 | 10 | + | 
 | 11 | +let totalEvals = 0;  | 
 | 12 | +let totalSuccesses = 0;  | 
 | 13 | + | 
 | 14 | +const apiHost = process.env.API_BASE_URL || "https://api.pipedream.com";  | 
 | 15 | + | 
 | 16 | +// json-diff shows the diff as __old and __new keys, which isn't descriptive,  | 
 | 17 | +// so we replace them with custom labels  | 
 | 18 | +function customDiff(original, updated, oldLabel = "expected", newLabel = "actual") {  | 
 | 19 | +  const result = diff(original, updated);  | 
 | 20 | + | 
 | 21 | +  function replaceLabels(obj) {  | 
 | 22 | +    if (Array.isArray(obj)) {  | 
 | 23 | +      return obj.map(replaceLabels);  | 
 | 24 | +    } else if (typeof obj !== "object" || obj === null) {  | 
 | 25 | +      return obj;  | 
 | 26 | +    }  | 
 | 27 | + | 
 | 28 | +    const newObj = {};  | 
 | 29 | +    for (const key in obj) {  | 
 | 30 | +      if (key === "__old") {  | 
 | 31 | +        newObj[oldLabel] = replaceLabels(obj[key]);  | 
 | 32 | +      } else if (key === "__new") {  | 
 | 33 | +        newObj[newLabel] = replaceLabels(obj[key]);  | 
 | 34 | +      } else {  | 
 | 35 | +        newObj[key] = replaceLabels(obj[key]);  | 
 | 36 | +      }  | 
 | 37 | +    }  | 
 | 38 | +    return newObj;  | 
 | 39 | +  }  | 
 | 40 | + | 
 | 41 | +  return replaceLabels(result);  | 
 | 42 | +}  | 
 | 43 | + | 
 | 44 | +async function processEvalFile(filePath) {  | 
 | 45 | +  try {  | 
 | 46 | +    const content = await fs.readFile(filePath, "utf-8");  | 
 | 47 | +    const evalData = JSON.parse(content);  | 
 | 48 | + | 
 | 49 | +    for (const evalTest of evalData.evaluationTests) {  | 
 | 50 | +      totalEvals++;  | 
 | 51 | +      const {  | 
 | 52 | +        query, triggers, actions,  | 
 | 53 | +      } = evalTest;  | 
 | 54 | + | 
 | 55 | +      const encodedQuery = encodeURIComponent(query);  | 
 | 56 | +      const apiUrl = `${apiHost}/v1/components/search?query=${encodedQuery}`;  | 
 | 57 | + | 
 | 58 | +      const response = await fetch(apiUrl, {  | 
 | 59 | +        headers: {  | 
 | 60 | +          "Content-Type": "application/json",  | 
 | 61 | +          "Authorization": `Bearer ${process.env.PIPEDREAM_API_KEY}`,  | 
 | 62 | +        },  | 
 | 63 | +      });  | 
 | 64 | +      const apiData = await response.json();  | 
 | 65 | + | 
 | 66 | +      // Compare actual and expected  | 
 | 67 | +      const apiTriggers = apiData?.triggers ?? [];  | 
 | 68 | +      const apiActions = apiData?.actions ?? [];  | 
 | 69 | +      const triggersMatch =  | 
 | 70 | +        JSON.stringify(apiTriggers.sort()) === JSON.stringify(triggers.sort());  | 
 | 71 | +      const actionsMatch =  | 
 | 72 | +        JSON.stringify(apiActions.sort()) === JSON.stringify(actions.sort());  | 
 | 73 | + | 
 | 74 | +      if (triggersMatch && actionsMatch) {  | 
 | 75 | +        totalSuccesses++;  | 
 | 76 | +        console.log(`${GREEN_CHECK} Success for query: "${query}"`);  | 
 | 77 | +      } else {  | 
 | 78 | +        console.log(`${RED_CROSS} Failure for query: "${query}"`);  | 
 | 79 | +        console.log("Differences:");  | 
 | 80 | +        console.log(customDiff({  | 
 | 81 | +          triggers,  | 
 | 82 | +          actions,  | 
 | 83 | +        }, apiData));  | 
 | 84 | +      }  | 
 | 85 | +    }  | 
 | 86 | +  } catch (error) {  | 
 | 87 | +    console.error(`Error processing file ${filePath}:`, error.message);  | 
 | 88 | +  }  | 
 | 89 | +}  | 
 | 90 | + | 
 | 91 | +async function main() {  | 
 | 92 | +  const evalFiles = process.argv.slice(2);  | 
 | 93 | + | 
 | 94 | +  if (evalFiles.length === 0) {  | 
 | 95 | +    console.error("Please provide at least one eval JSON file.");  | 
 | 96 | +    process.exit(1);  | 
 | 97 | +  }  | 
 | 98 | + | 
 | 99 | +  for (const file of evalFiles) {  | 
 | 100 | +    const filePath = path.resolve(file);  | 
 | 101 | +    await processEvalFile(filePath);  | 
 | 102 | +  }  | 
 | 103 | + | 
 | 104 | +  const successRate = ((totalSuccesses / totalEvals) * 100).toFixed(2);  | 
 | 105 | +  console.log(`\nTotal Evals: ${totalEvals}`);  | 
 | 106 | +  console.log(`Total Successes: ${totalSuccesses}`);  | 
 | 107 | +  console.log(`Success Rate: ${successRate}%`);  | 
 | 108 | +}  | 
 | 109 | + | 
 | 110 | +main();  | 
0 commit comments