import fs from 'fs'; import { fetchExecutions, fetchAllRunningExecutions, createExecution, deleteExecution } from "../api/crud"; import { Execution } from "../types/execution.types"; import { asyncAction } from "../utils/asyncActions"; import { assertNonInteractiveCreateInput, assertNonInteractiveStartRunInput, BasicExecutionYamlOptions, createYamlBasic, log, printHelp, showCreateTemplate } from "../utils/general"; import { printExecutionsHistory, printExecutionsTable, printRunningExecutionsTable } from "../output/printFormattedTables"; import { createExecutionObjectFromFile, initiateRun, initiateRunByName, terminateRun } from '../utils/executions'; import * as p from '@clack/prompts'; import { getExecutionHistory, getStatus } from '../api/execute'; import { ExecutionHistory, RunnningExecution } from '../types/types'; import { askFilePath, askWhichExecutionToRun, askWhichExecutionToStop, askWhichOptionOnCreate } from '../prompts/asks'; import { cache } from '..'; export async function handleGetAll(printTable = true) { const projectId = process.env.projectId ?? ""; const allExecutions: Execution[] = await asyncAction(async () => await fetchExecutions(), 10000, 'Loading...'); if (allExecutions == null) { log("❌ No data returned"); throw new Error("Internal Server Error: Please try again later."); } log('✅ Fetched all executions', JSON.stringify(allExecutions, null, 2)); printTable && await printExecutionsTable(allExecutions); return allExecutions; } export async function handleGetActive() { const projectId = process.env.projectId ?? ""; const allRunningExecutions: RunnningExecution[] = await asyncAction(async () => await fetchAllRunningExecutions(projectId), 10000, 'Loading...'); if (allRunningExecutions == null) { log("❌ No data returned"); throw new Error("Internal Server Error: Please try again later."); } else { log('✅ Result of fetching all running executions', JSON.stringify(allRunningExecutions, null, 2)); } printRunningExecutionsTable(allRunningExecutions); return allRunningExecutions; } export async function handleCreateNonInteractive(args: any) { assertNonInteractiveCreateInput(args); const basicExecutionOptions: BasicExecutionYamlOptions = { env: args.env, name: args.name, branch: args.branch, uploadVideo: args.uploadFailedVideos === 'true', retryCount: args.retryCount ? parseInt(args.retryCount) : undefined, }; if (args.env && args.name && args.branch) { if (args.tag) { basicExecutionOptions.tag = args.tag; } else if (args.tags) { basicExecutionOptions.tags = args.tags.split(',').map((tag: string) => tag.trim()); } else { if (args.group1Tags && args.group2Tags) { basicExecutionOptions.group1Tags = args.group1Tags.split(',').map((tag: string) => tag.trim()); basicExecutionOptions.group2Tags = args.group2Tags.split(',').map((tag: string) => tag.trim()); } else { throw new Error('Unsupported format for create mode, these are the 3 supported formats:\n1. --env --name --branch --tag \n2. --env --name --branch --tags ""\n3. --env --name --branch --group1Tags "" --group2Tags=""'); } } basicExecutionOptions.customTestData = cache.getInnerObject('testData') ?? {}; const fileContent = createYamlBasic(basicExecutionOptions); await asyncAction(async () => { const execution = await createExecutionObjectFromFile(fileContent); // console.log('📄 Created execution object from file:', JSON.stringify(execution, null, 2)); await createExecution(execution); }, 10000, 'Creating...'); return; } if (!args.filePath) { throw new Error('Unsupported mode, these are the 4 supported format:\n1. --env --name --branch --tag TEST_DATA_KEY=value\n2. --env --name --branch --tags "" TEST_DATA_KEY=value\n3. --env --name --branch --group1Tags "" --group2Tags="" TEST_DATA_KEY=value\n4. --filePath '); } const filePath = args.filePath as string; await asyncAction(async () => { const fileContent = await fs.promises.readFile(filePath, 'utf-8'); const execution = await createExecutionObjectFromFile(fileContent); log('📄 Created execution object from file:', JSON.stringify(execution, null, 2)); await createExecution(execution); }, 10000, 'Creating...'); } export async function handleDeleteNonInteractive(args: any) { if (!args.name) { throw new Error('Execution name is required. Please provide --name '); } const executionName = args.name as string; await asyncAction(async () => { await deleteExecution(executionName); log('📄 Deleted execution:', executionName); }, 10000, 'Deleting...'); } export async function handleCreateInteractive() { await showCreateTemplate(); const startChoice = await askWhichOptionOnCreate(); if (p.isCancel(startChoice) || startChoice !== 'input-path') { return; } const filePath = await askFilePath(); if (p.isCancel(filePath)) { return; } await asyncAction(async () => { let fileContent: string; try { fileContent = await fs.promises.readFile(filePath, 'utf-8'); } catch (err: any) { if (err.code === "ENOENT") { p.outro(`❌ File not found at: ${filePath} Please check your path and try again.`); } else { p.note(`❌ Failed to read file: ${err.message}`, 'File Read Error'); } return; } const execution = await createExecutionObjectFromFile(fileContent); log('📄 Created execution object from file:', JSON.stringify(execution, null, 2)); await createExecution(execution); p.outro('✅ Created a new execution based on the configuration successfully.'); }, 10000, 'Creating...'); } export async function handleStartNewRun() { let executions: Execution[] = await handleGetAll(); if (!executions.length) { p.outro("No executions found for this project, please create one first."); return; } const executionId = await askWhichExecutionToRun(executions); if (!executionId) return; await initiateRun({ executionId }); } export async function handleGetStatusAsync(args: any) { if (!args.executionInstanceId) { throw new Error('Execution Instance ID is required. Please provide --executionInstanceId='); } const executionInstanceId = args.executionInstanceId as string; const data = await getStatus(executionInstanceId); if (data) { console.log('\n', JSON.stringify(data, null, 2)); } } export async function handleStartNewRunNonInteractive(args: any) { assertNonInteractiveStartRunInput(args); if (!args.executionId && !args.executionName) { throw new Error('Execution ID or Name is required. Please provide --executionId= or --executionName='); } args.executionId ? await initiateRun(args) : await initiateRunByName(args); } export async function handleStopRun() { const runningExecutions = await handleGetActive(); if (!runningExecutions.length) { p.outro("No running executions found for this project."); return; } const executionId = await askWhichExecutionToStop(runningExecutions); if (!executionId) return; await terminateRun(executionId); } export async function handleStopRunNonInteractive(args: any) { if (!args.executionInstanceId) { throw new Error('Execution Instance ID is required. Please provide --executionInstanceId '); } await terminateRun(args.executionInstanceId); } export async function handleGetHistory(count = 5) { const data: any[] = await asyncAction(async () => await getExecutionHistory(count), 10000, 'Loading...'); const allCompletedExecutions: ExecutionHistory[] = data.filter((exec) => exec && !(exec.running)).map((item: any) => { const finalStatus = item.statusHistory && item.statusHistory.length > 0 ? item.statusHistory[0].status : { verboseStatus: 'Unknown', totalScenarios: 0, scenariosPassed: 0, scenariosFailed: 0 }; return { instanceId: item._id, name: item.name, executionId: item.executionId, reportLink: item.reportLink, startedAt: new Date(item.createdAt), endedAt: new Date(item.updatedAt), projectId: item.projectId, finalStatus: finalStatus, } }); printExecutionsHistory(allCompletedExecutions); return data; } export async function handleGetHelp(args: any) { if (args.help || args.h) { printHelp(); process.exit(0); } }