Download benchmarks/cli.ts from SaylorTwift/forgecode: direct link, hf CLI and curl.
- Browser
- Download file 11.5 kB
-
https://huggingface.co/SaylorTwift/forgecode/resolve/main/benchmarks/cli.ts
- Command line
-
hf download hf://SaylorTwift/forgecode/benchmarks/cli.ts
-
curl -L -o cli.ts https://huggingface.co/SaylorTwift/forgecode/resolve/main/benchmarks/cli.ts
11.5 kB
| // Handle EPIPE errors gracefully (e.g., when piping to `head` or `jq` that closes early) | |
| process.stdout.on("error", (error: NodeJS.ErrnoException) => { | |
| if (error.code === "EPIPE") { | |
| process.exit(0); | |
| } | |
| throw error; | |
| }); | |
| import * as fs from "fs/promises"; | |
| import * as path from "path"; | |
| import { fileURLToPath } from "url"; | |
| import { parse as parseYaml } from "yaml"; | |
| import { exec } from "child_process"; | |
| import { promisify } from "util"; | |
| import pLimit from "p-limit"; | |
| import pino from "pino"; | |
| import { TaskStatus, type Task } from "./model.js"; | |
| import { | |
| getContextsFromSources, | |
| generateCommand, | |
| } from "./command-generator.js"; | |
| import { parseCliArgs } from "./parse.js"; | |
| import { executeTask, type TaskExecutionResult } from "./task-executor.js"; | |
| import { processValidations, type ValidationResult } from "./verification.js"; | |
| import { createTempDir, parseCsvAsync } from "./utils.js"; | |
| const execAsync = promisify(exec); | |
| export type TaskResult = { | |
| index: number; | |
| status: TaskStatus; | |
| command: string; | |
| duration: number; | |
| validationResults: ValidationResult[]; | |
| }; | |
| // ESM compatibility for __dirname | |
| const __filename = fileURLToPath(import.meta.url); | |
| const __dirname = path.dirname(__filename); | |
| /** | |
| * Create logger instance | |
| * - Human-readable CLI output by default | |
| * - Set LOG_JSON=1 for machine-readable JSON output (for piping to jq, log aggregators, etc.) | |
| */ | |
| const logger = | |
| process.env.LOG_JSON === "1" | |
| ? pino({ | |
| level: process.env.LOG_LEVEL || "info", | |
| formatters: { | |
| level: (label) => ({ level: label }), | |
| }, | |
| timestamp: pino.stdTimeFunctions.isoTime, | |
| }) | |
| : pino({ | |
| level: process.env.LOG_LEVEL || "info", | |
| transport: { | |
| target: "pino-pretty", | |
| options: { | |
| colorize: true, | |
| translateTime: "HH:MM:ss", | |
| ignore: "pid,hostname", | |
| messageFormat: "{msg}", | |
| }, | |
| }, | |
| formatters: { | |
| level: (label) => ({ level: label }), | |
| }, | |
| timestamp: pino.stdTimeFunctions.isoTime, | |
| }); | |
| async function main() { | |
| // Parse command line arguments | |
| let args; | |
| try { | |
| args = await parseCliArgs(__dirname); | |
| } catch (error) { | |
| const message = error instanceof Error ? error.message : "Unknown error"; | |
| logger.error({ error: message }, "Failed to parse CLI arguments"); | |
| process.exit(1); | |
| } | |
| const { evalName, evalDir, taskFile } = args; | |
| // Check if eval directory and task file exist | |
| try { | |
| await fs.access(evalDir); | |
| } catch { | |
| logger.error({ evalDir }, "Eval directory not found"); | |
| process.exit(1); | |
| } | |
| try { | |
| await fs.access(taskFile); | |
| } catch { | |
| logger.error({ evalDir }, "task.yml not found"); | |
| process.exit(1); | |
| } | |
| // Read and parse task.yml | |
| const taskContent = await fs.readFile(taskFile, "utf-8"); | |
| const task: Task = parseYaml(taskContent); | |
| // Display header | |
| const displayName = path.relative(__dirname, evalDir) || evalName; | |
| // Create debug directory with timestamp | |
| const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); | |
| const debugDir = path.join(evalDir, "debug", timestamp); | |
| await fs.mkdir(debugDir, { recursive: true }); | |
| // Create a temp directory for setup commands | |
| const setupTmpDir = await createTempDir("forge-setup-"); | |
| // Execute before_run commands | |
| if (task.before_run && task.before_run.length > 0) { | |
| for (const cmd of task.before_run) { | |
| try { | |
| logger.info( | |
| { dir: setupTmpDir.name, command: cmd }, | |
| "Running setup command", | |
| ); | |
| // Small delay to allow logger to flush before command output | |
| await new Promise((resolve) => setTimeout(resolve, 0)); | |
| await execAsync(cmd, { | |
| cwd: setupTmpDir.name, | |
| }); | |
| } catch (error) { | |
| logger.error({ command: cmd }, "Setup command failed"); | |
| process.exit(1); | |
| } | |
| } | |
| } | |
| // Load data from sources and create cross product | |
| const sourcesData: Record<string, string>[][] = []; | |
| for (const source of task.sources) { | |
| if ("csv" in source) { | |
| const csvPath = path.join(evalDir, source.csv); | |
| try { | |
| await fs.access(csvPath); | |
| } catch { | |
| logger.error({ csvPath }, "CSV file not found"); | |
| process.exit(1); | |
| } | |
| const csvContent = await fs.readFile(csvPath, "utf-8"); | |
| const csvData = await parseCsvAsync(csvContent, { | |
| columns: true, | |
| skip_empty_lines: true, | |
| }); | |
| sourcesData.push(csvData); | |
| } else if ("cmd" in source) { | |
| logger.error("cmd source type not yet implemented"); | |
| process.exit(1); | |
| } else if ("value" in source) { | |
| sourcesData.push(source.value); | |
| } | |
| } | |
| // Create cross product of all sources | |
| if (sourcesData.length === 0) { | |
| logger.error("No sources configured"); | |
| process.exit(1); | |
| } | |
| // Get contexts from sources using pure function | |
| const data = getContextsFromSources(sourcesData); | |
| const results: TaskResult[] = []; | |
| // Get parallelism setting (default to 1 for sequential execution) | |
| const parallelism = task.parallelism ?? 1; | |
| const limit = pLimit(parallelism); | |
| // Execute run command for each data row | |
| // Create promises for all tasks | |
| const taskPromises = data.map((row, i) => { | |
| return limit(async () => { | |
| // Create a unique temp directory for this task | |
| const taskTmpDir = await createTempDir(`forge-task-${i + 1}-`); | |
| // Create a 'task' subdirectory for running commands | |
| const taskWorkDir = path.join(taskTmpDir.name, 'task'); | |
| await fs.mkdir(taskWorkDir, { recursive: true }); | |
| const logFile = path.join(taskTmpDir.name, `task.log`); | |
| // Context for command interpolation and validations | |
| const context = { ...row, dir: taskTmpDir.name }; | |
| // Support both single command and multiple commands | |
| const commands = Array.isArray(task.run) ? task.run : [task.run]; | |
| // Filter out empty or non-string commands | |
| const validCommands = commands.filter(cmd => typeof cmd === 'string' && cmd.trim().length > 0); | |
| // If no valid commands, skip this task | |
| if (validCommands.length === 0) { | |
| logger.warn({ task_id: i + 1 }, "No valid commands found, skipping task"); | |
| return { | |
| index: i + 1, | |
| status: TaskStatus.Failed, | |
| command: "No valid commands", | |
| duration: 0, | |
| validationResults: [], | |
| }; | |
| } | |
| let combinedOutput = ""; | |
| let totalDuration = 0; | |
| let lastError: string | undefined; | |
| let hasTimeout = false; | |
| let hasEarlyExit = false; | |
| // Log task launch once before executing commands | |
| logger.info( | |
| { | |
| task_id: i + 1, | |
| total_commands: validCommands.length, | |
| log: logFile, | |
| dir: taskTmpDir.name, | |
| work_dir: taskWorkDir, | |
| parameters: context, | |
| }, | |
| "Launching task", | |
| ); | |
| // Execute commands sequentially | |
| for (let cmdIdx = 0; cmdIdx < validCommands.length; cmdIdx++) { | |
| const commandTemplate = validCommands[cmdIdx]!; // Non-null assertion safe after filter | |
| const command = generateCommand(commandTemplate, context); | |
| logger.info( | |
| { | |
| command, | |
| task_id: i + 1, | |
| command_id: cmdIdx + 1, | |
| total_commands: validCommands.length, | |
| }, | |
| "Executing command", | |
| ); | |
| const executionResult = await executeTask( | |
| command, | |
| i + 1, | |
| logFile, | |
| taskWorkDir, // Use taskWorkDir instead of taskTmpDir.name | |
| task, | |
| context, | |
| cmdIdx > 0, // append if this is not the first command | |
| ); | |
| totalDuration += executionResult.duration; | |
| if (executionResult.output) { | |
| combinedOutput += executionResult.output; | |
| } | |
| if (executionResult.earlyExit) { | |
| hasEarlyExit = true; | |
| } | |
| // If execution failed or timed out, stop executing remaining commands | |
| if (executionResult.error) { | |
| lastError = executionResult.error; | |
| hasTimeout = executionResult.isTimeout; | |
| logger.warn( | |
| { | |
| task_id: executionResult.index, | |
| command: executionResult.command, | |
| command_id: cmdIdx + 1, | |
| duration: executionResult.duration, | |
| error: executionResult.error, | |
| is_timeout: executionResult.isTimeout, | |
| }, | |
| executionResult.isTimeout ? "Task timed out" : "Task failed", | |
| ); | |
| break; | |
| } | |
| } | |
| // If any command failed, return failure result | |
| if (lastError) { | |
| const { validationResults } = await processValidations( | |
| combinedOutput, | |
| task, | |
| logger, | |
| i + 1, | |
| totalDuration, | |
| logFile, | |
| context, | |
| ); | |
| return { | |
| index: i + 1, | |
| status: hasTimeout ? TaskStatus.Timeout : TaskStatus.Failed, | |
| command: validCommands.length === 1 ? validCommands[0]! : `${validCommands.length} commands`, | |
| duration: totalDuration, | |
| validationResults, | |
| }; | |
| } | |
| // Run validations on the combined output | |
| const { validationResults, status: validationStatus } = | |
| await processValidations( | |
| combinedOutput, | |
| task, | |
| logger, | |
| i + 1, | |
| totalDuration, | |
| logFile, | |
| context, | |
| ); | |
| return { | |
| index: i + 1, | |
| status: | |
| validationStatus === "passed" | |
| ? TaskStatus.Passed | |
| : TaskStatus.ValidationFailed, | |
| command: validCommands.length === 1 ? validCommands[0]! : `${validCommands.length} commands`, | |
| duration: totalDuration, | |
| validationResults, | |
| }; | |
| }); | |
| }); | |
| // Wait for all tasks to complete | |
| const taskResults = await Promise.all(taskPromises); | |
| results.push(...taskResults); | |
| // Calculate summary statistics | |
| const successCount = results.filter( | |
| (r) => r.status === TaskStatus.Passed, | |
| ).length; | |
| const warningCount = results.filter( | |
| (r) => r.status === TaskStatus.ValidationFailed, | |
| ).length; | |
| const timeoutCount = results.filter( | |
| (r) => r.status === TaskStatus.Timeout, | |
| ).length; | |
| const failCount = results.filter( | |
| (r) => r.status === TaskStatus.Failed, | |
| ).length; | |
| const totalDuration = results.reduce((sum, r) => sum + r.duration, 0); | |
| // Calculate validation statistics | |
| const totalValidations = results.reduce( | |
| (sum, r) => sum + r.validationResults.length, | |
| 0, | |
| ); | |
| const passedValidations = results.reduce( | |
| (sum, r) => sum + r.validationResults.filter((v) => v.passed).length, | |
| 0, | |
| ); | |
| // Print summary | |
| logger.info( | |
| { | |
| total: results.length, | |
| passed: successCount, | |
| validation_failed: warningCount, | |
| timeout: timeoutCount, | |
| failed: failCount, | |
| total_duration: totalDuration, | |
| validations: { | |
| total: totalValidations, | |
| passed: passedValidations, | |
| failed: totalValidations - passedValidations, | |
| }, | |
| dir: setupTmpDir.name, | |
| }, | |
| "Evaluation completed", | |
| ); | |
| // Exit with error code if any task failed (excluding timeouts and validation failures) | |
| if (failCount > 0) { | |
| process.exit(1); | |
| } | |
| // Exit successfully - ensures process terminates even with open handles | |
| process.exit(0); | |
| } | |
| main().catch((error) => { | |
| logger.error({ error: error.message }, "Fatal error"); | |
| process.exit(1); | |
| }); | |