@labnex/cli
Version:
CLI for Labnex, an AI-Powered Testing Automation Platform
415 lines ⢠17.6 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.apiClient = exports.LabnexApiClient = void 0;
const axios_1 = __importDefault(require("axios"));
const chalk_1 = __importDefault(require("chalk"));
const config_1 = require("../utils/config");
const cli_table3_1 = __importDefault(require("cli-table3"));
class LabnexApiClient {
constructor() {
this.verboseLogging = false;
this.api = axios_1.default.create({
timeout: 120000,
headers: {
'Content-Type': 'application/json',
},
});
// Request interceptor to add auth token
this.api.interceptors.request.use(async (config) => {
const userConfig = await (0, config_1.loadConfig)();
// Verbose if set in config OR via global env flag (e.g. --verbose passed to CLI)
this.verboseLogging = process.env.LABNEX_VERBOSE === 'true' || userConfig.verbose || false;
config.baseURL = userConfig.apiUrl;
if (userConfig.token) {
config.headers.Authorization = `Bearer ${userConfig.token}`;
}
if (this.verboseLogging) {
console.log(chalk_1.default.gray(`ā ${config.method?.toUpperCase()} ${config.url}`));
}
return config;
});
// Response interceptor for error handling
this.api.interceptors.response.use((response) => {
return response;
}, (error) => {
if (error.response && (error.response.status === 401 || error.response.status === 403)) {
const isAiEndpoint = error.config?.url?.includes('/ai') || error.config?.url?.includes('/openai') || error.config?.url?.includes('/ai-');
if (isAiEndpoint) {
console.error(chalk_1.default.red('AI optimization failed ā check your API key or permissions.'));
return Promise.reject(error);
}
}
if (error.response?.status === 401) {
console.error(chalk_1.default.red('Authentication failed. Please run: labnex auth login'));
process.exit(1);
}
if (error.response?.status >= 500) {
console.error(chalk_1.default.red('Server error. Please try again later.'));
}
return Promise.reject(error);
});
}
setVerbose(verbose) {
this.verboseLogging = verbose;
}
/**
* Allow commands to turn on verbose logging after the client has been constructed.
* (Helpful for sub-commands that parse --verbose themselves.)
*/
enableVerbose() {
this.verboseLogging = true;
}
// Authentication
async login(email, password) {
const response = await this.api.post('/auth/login', { email, password });
// Handle both old and new response formats
if (response.data.success) {
// New format: { success: true, data: { user, token } }
return response.data;
}
else if (response.data.user && response.data.token) {
// Old format: { user, token }
return {
success: true,
data: {
user: response.data.user,
token: response.data.token
}
};
}
else {
return {
success: false,
data: { user: null, token: '' },
error: 'Invalid response format'
};
}
}
async me() {
const response = await this.api.get('/auth/me');
return response.data;
}
// Projects
async getProjects() {
try {
const response = await this.api.get('/projects');
// Backend returns projects directly, not wrapped in success/data format
return {
success: true,
data: response.data
};
}
catch (error) {
return {
success: false,
data: [],
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
async getProject(projectId) {
try {
const response = await this.api.get(`/projects/${projectId}`);
return {
success: true,
data: response.data
};
}
catch (error) {
return {
success: false,
data: {
_id: '',
name: '',
projectCode: '',
description: '',
isActive: false,
testCaseCount: 0,
taskCount: 0,
owner: {
_id: '',
name: '',
email: ''
},
members: [],
createdAt: '',
updatedAt: ''
},
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
async displayProjectDetails(project) {
console.log(chalk_1.default.cyan(`\nš ${project.name} (${project.projectCode})`));
console.log(chalk_1.default.gray('ā'.repeat(50)));
console.log(`${chalk_1.default.bold('Description:')} ${project.description || 'No description'}`);
console.log(`${chalk_1.default.bold('Status:')} ${project.isActive ? chalk_1.default.green('Active') : chalk_1.default.gray('Inactive')}`);
console.log(`${chalk_1.default.bold('Owner:')} ${project.owner.name} (${project.owner.email})`);
console.log(`${chalk_1.default.bold('Created:')} ${new Date(project.createdAt).toLocaleDateString()}`);
console.log(`${chalk_1.default.bold('Updated:')} ${new Date(project.updatedAt).toLocaleDateString()}`);
console.log(`\n${chalk_1.default.bold('Statistics:')}`);
console.log(` Test Cases: ${chalk_1.default.cyan(project.testCaseCount)}`);
console.log(` Tasks: ${chalk_1.default.cyan(project.taskCount)}`);
console.log(` Team Members: ${chalk_1.default.cyan(project.members.length)}`);
if (project.members.length > 0) {
console.log(`\n${chalk_1.default.bold('Team Members:')}`);
const membersTable = new cli_table3_1.default({
head: ['Name', 'Email', 'Role'],
colWidths: [20, 30, 15]
});
project.members.forEach((member) => {
membersTable.push([
member.name,
member.email,
member.role
]);
});
console.log(membersTable.toString());
}
}
async createProject(project) { const response = await this.api.post('/projects', project); return { success: true, data: response.data }; }
// Test Cases
async getTestCases(projectId) {
try {
const response = await this.api.get(`/projects/${projectId}/test-cases`);
// Assuming response.data is the array of TestCase objects, wrap it
return {
success: true,
data: response.data
};
}
catch (error) {
return {
success: false,
data: [],
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
async createTestCase(projectId, testCase) {
try {
if (this.verboseLogging) {
console.log(chalk_1.default.gray(`[DEBUG] Creating test case for project ${projectId}`));
console.log(chalk_1.default.gray(`[DEBUG] Test case data: ${JSON.stringify(testCase, null, 2)}`));
}
const response = await this.api.post(`/projects/${projectId}/test-cases`, testCase);
if (this.verboseLogging) {
console.log(chalk_1.default.gray(`[DEBUG] Response status: ${response.status}`));
console.log(chalk_1.default.gray(`[DEBUG] Response data: ${JSON.stringify(response.data, null, 2)}`));
}
// Temporary workaround: Assume success if HTTP status is 200 or 201, regardless of response content
if (response.status === 200 || response.status === 201) {
console.log(chalk_1.default.yellow('[DEBUG] Assuming test case save success based on HTTP status ' + response.status + '. Test case likely saved on backend.'));
return {
success: true,
data: response.data || { _id: 'unknown', title: testCase.title, description: testCase.description || '', steps: testCase.steps, expectedResult: testCase.expectedResult, priority: testCase.priority, status: 'PENDING', projectId: projectId, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }
};
}
// Check if response data indicates success or contains an error
if (response.data && response.data.success === false) {
return {
success: false,
data: null,
error: response.data.error || response.data.message || 'API reported failure'
};
}
if (!response.data || (response.data._id === undefined && response.data.id === undefined)) {
return {
success: false,
data: null,
error: 'Invalid response format or missing test case ID'
};
}
return {
success: true,
data: response.data
};
}
catch (error) {
if (this.verboseLogging) {
if (error instanceof Error) {
let errorMessage = error.message;
if (error.response?.data) {
errorMessage += `\n${JSON.stringify(error.response.data)}`;
}
console.log(chalk_1.default.red(`[DEBUG] Error creating test case: ${errorMessage}`));
}
else {
console.log(chalk_1.default.red(`[DEBUG] Unknown error creating test case: ${error}`));
}
}
return {
success: false,
data: null,
error: error instanceof Error ? error.message : 'Unknown error during test case creation'
};
}
}
// Update existing test case (PUT)
async updateTestCase(projectId, testCaseId, updates) {
try {
const response = await this.api.put(`/projects/${projectId}/test-cases/${testCaseId}`, updates);
return { success: true, data: response.data };
}
catch (error) {
return { success: false, data: null, error: error instanceof Error ? error.message : 'Unknown error' };
}
}
// Test Runs
async createTestRun(projectId, config) {
const response = await this.api.post(`/projects/${projectId}/test-runs`, config);
return response.data;
}
async getTestRun(runId) {
const response = await this.api.get(`/test-runs/${runId}`);
return response.data;
}
async getTestRunResults(runId) {
const response = await this.api.get(`/test-runs/${runId}/results`);
return response.data;
}
// Test Runs list for a project
async getTestRuns(projectId) {
const candidatePaths = [
`/projects/${projectId}/runs`,
`/projects/${projectId}/test-runs`, // alt pattern
{ path: '/runs', params: { projectId } }, // query param style
{ path: '/test-runs', params: { projectId } }
];
for (const c of candidatePaths) {
try {
const res = typeof c === 'string'
? await this.api.get(c)
: await this.api.get(c.path, { params: c.params });
const possibleArray = Array.isArray(res.data)
? res.data
: (res.data && res.data.success && Array.isArray(res.data.data) ? res.data.data : null);
if (possibleArray !== null) {
return { success: true, data: possibleArray };
}
}
catch (e) {
if (e.response?.status !== 404) {
// Non-404 error, stop trying further
return {
success: false,
data: [],
error: e instanceof Error ? e.message : 'Unknown error'
};
}
// else continue to next pattern
}
}
return { success: false, data: [], error: 'Runs endpoint not found (tried multiple variants)' };
}
// AI Features
async generateTestCase(description) {
const response = await this.api.post('/ai/generate-test-case', { description });
return response.data;
}
async optimizeTestSuite(projectId, codeChanges) {
if (this.verboseLogging) {
console.log(chalk_1.default.gray(`[DEBUG] Optimizing test suite for project ${projectId}`));
console.log(chalk_1.default.gray(`[DEBUG] Payload: ${JSON.stringify({ codeChanges }, null, 2)}`));
}
const response = await this.api.post(`/ai/optimize-test-suite/${projectId}`, { codeChanges });
if (this.verboseLogging) {
console.log(chalk_1.default.gray(`[DEBUG] Response status: ${response.status}`));
console.log(chalk_1.default.gray(`[DEBUG] Response data: ${JSON.stringify(response.data, null, 2)}`));
}
return response.data;
}
// Failure Analysis
async analyzeFailure(testRunId, failureId) {
const response = await this.api.post(`/ai/analyze-failure`, { testRunId, failureId });
return response.data;
}
async analyzeFailureConversational(testRunId, failureId, conversationHistory, question) {
const response = await this.api.post('/ai/analyze-failure-conversational', {
testRunId,
failureId,
conversationHistory,
question
});
return response.data;
}
// AI-assisted Test Step Execution
async interpretTestStep(stepDescription) {
try {
if (this.verboseLogging) {
console.log(chalk_1.default.gray(`[DEBUG] Interpreting step: "${stepDescription}"`));
}
const response = await this.api.post('/ai/interpret-step', {
description: stepDescription,
});
if (this.verboseLogging) {
console.log(chalk_1.default.gray(`[DEBUG] Interpretation response: ${JSON.stringify(response.data)}`));
}
if (response.data.success) {
return {
success: true,
data: response.data.data.interpretedStep // Assuming the API returns { success: true, data: { interpretedStep: '...' } }
};
}
else {
return {
success: false,
data: '',
error: response.data.error || 'Failed to interpret step'
};
}
}
catch (error) {
return {
success: false,
data: '',
error: error.response?.data?.message || error.message || 'Unknown error'
};
}
}
async suggestAlternative(step, pageContext = '') {
try {
if (this.verboseLogging) {
console.log(chalk_1.default.gray(`[DEBUG] Suggesting alternative for step: "${step}"`));
console.log(chalk_1.default.gray(`[DEBUG] Page context length: ${pageContext.length}`));
}
const response = await this.api.post('/ai/suggest-alternative', {
step,
pageContext,
});
if (this.verboseLogging) {
console.log(chalk_1.default.gray(`[DEBUG] Suggestion response: ${JSON.stringify(response.data)}`));
}
return response.data; // Assuming API returns ApiResponse<string> directly
}
catch (error) {
if (this.verboseLogging) {
console.log(chalk_1.default.red(`[DEBUG] Error suggesting alternative: ${error.message}`));
}
return {
success: false,
data: '',
error: error.response?.data?.message || error.message || 'Unknown error',
};
}
}
async getDynamicSelectorSuggestion(context) {
try {
const response = await this.api.post('/ai/suggest-selector', context);
return response.data;
}
catch (error) {
return {
success: false,
data: { suggestedSelector: '' },
error: error.response?.data?.message || error.message || 'Unknown error'
};
}
}
}
exports.LabnexApiClient = LabnexApiClient;
exports.apiClient = new LabnexApiClient();
//# sourceMappingURL=client.js.map