diff --git a/src/commands/live.ts b/src/commands/live.ts index 081c9bc..544f592 100644 --- a/src/commands/live.ts +++ b/src/commands/live.ts @@ -41,26 +41,11 @@ const startSub = defineCommand({ logger.log(`${symbols.running} Starting ${platform} live session...`); - const res = await fetch(`${apiUrl}/live`, { - body: JSON.stringify({ binaryUploadId: binaryId, platform }), - headers: { - 'content-type': 'application/json', - ...auth.headers, - }, - method: 'POST', + const session = await ApiGateway.startLiveSession(apiUrl, auth, { + binaryUploadId: binaryId, + platform, }); - if (!res.ok) { - await ApiGateway.handleApiError(res, 'Failed to start live session'); - } - - const session = (await res.json()) as { - id: number; - platform: string; - session_name: string; - status: string; - }; - const frontendUrl = resolveFrontendUrl(apiUrl); logger.log(`${symbols.success} Live session started`); @@ -104,18 +89,7 @@ const installSub = defineCommand({ `${symbols.running} Installing binary ${colors.highlight(binaryId)} on session ${colors.highlight(sessionName)}...`, ); - const res = await fetch(`${apiUrl}/live/${sessionName}/install`, { - body: JSON.stringify({ binaryUploadId: binaryId }), - headers: { - 'content-type': 'application/json', - ...auth.headers, - }, - method: 'POST', - }); - - if (!res.ok) { - await ApiGateway.handleApiError(res, 'Failed to install binary'); - } + await ApiGateway.installLiveBinary(apiUrl, auth, sessionName, binaryId); logger.log(`${symbols.success} Binary installed successfully`); }, @@ -138,24 +112,7 @@ const execSub = defineCommand({ `${symbols.running} Executing commands on session ${colors.highlight(sessionName)}...`, ); - const res = await fetch(`${apiUrl}/live/${sessionName}/exec`, { - body: JSON.stringify({ yaml }), - headers: { - 'content-type': 'application/json', - ...auth.headers, - }, - method: 'POST', - }); - - if (!res.ok) { - await ApiGateway.handleApiError(res, 'Failed to execute test'); - } - - const result = (await res.json()) as { - error?: string; - output?: string; - success: boolean; - }; + const result = await ApiGateway.execLiveYaml(apiUrl, auth, sessionName, yaml); logger.log( result.success @@ -188,14 +145,7 @@ const stopSub = defineCommand({ logger.log(`${symbols.running} Stopping session ${colors.highlight(sessionName)}...`); - const res = await fetch(`${apiUrl}/live/${sessionName}`, { - headers: { ...auth.headers }, - method: 'DELETE', - }); - - if (!res.ok) { - await ApiGateway.handleApiError(res, 'Failed to stop session'); - } + await ApiGateway.stopLiveSession(apiUrl, auth, sessionName); logger.log(`${symbols.success} Session stopped`); }, @@ -212,23 +162,7 @@ const statusSub = defineCommand({ const apiUrl = args['api-url'] as string; const sessionName = args.session as string; - const res = await fetch(`${apiUrl}/live/${sessionName}`, { - headers: { ...auth.headers }, - method: 'GET', - }); - - if (!res.ok) { - await ApiGateway.handleApiError(res, 'Failed to get session status'); - } - - const session = (await res.json()) as { - binary_upload_id: null | string; - created_at: string; - id: number; - platform: string; - session_name: string; - status: string; - }; + const session = await ApiGateway.getLiveSession(apiUrl, auth, sessionName); logger.log(sectionHeader('Live Session')); logger.log(` ${colors.dim('Session:')} ${colors.highlight(session.session_name)}`); diff --git a/src/gateways/api-gateway.ts b/src/gateways/api-gateway.ts index 022d9bc..028b0ed 100644 --- a/src/gateways/api-gateway.ts +++ b/src/gateways/api-gateway.ts @@ -6,6 +6,11 @@ import { pipeline } from 'node:stream/promises'; import { TAppMetadata } from '../types'; import type { AuthContext } from '../types/domain/auth.types'; +import type { + LiveExecResult, + LiveSession, + LiveSessionSummary, +} from '../types/domain/live.types'; import { paths } from '../types/generated/schema.types'; /** @@ -574,4 +579,141 @@ export const ApiGateway = { throw error; } }, + + async startLiveSession( + baseUrl: string, + auth: AuthContext, + params: { binaryUploadId?: string; platform: string }, + ): Promise { + try { + const res = await fetch(`${baseUrl}/live`, { + body: JSON.stringify({ + binaryUploadId: params.binaryUploadId, + platform: params.platform, + }), + headers: { + 'content-type': 'application/json', + ...auth.headers, + }, + method: 'POST', + }); + if (!res.ok) { + await this.handleApiError(res, 'Failed to start live session'); + } + + return await parseJsonResponse(res, 'Failed to start live session'); + } catch (error) { + if (error instanceof TypeError && error.message === 'fetch failed') { + throw this.enhanceFetchError(error, `${baseUrl}/live`); + } + + throw error; + } + }, + + async installLiveBinary( + baseUrl: string, + auth: AuthContext, + sessionName: string, + binaryUploadId: string, + ): Promise { + const url = `${baseUrl}/live/${sessionName}/install`; + try { + const res = await fetch(url, { + body: JSON.stringify({ binaryUploadId }), + headers: { + 'content-type': 'application/json', + ...auth.headers, + }, + method: 'POST', + }); + if (!res.ok) { + await this.handleApiError(res, 'Failed to install binary'); + } + } catch (error) { + if (error instanceof TypeError && error.message === 'fetch failed') { + throw this.enhanceFetchError(error, url); + } + + throw error; + } + }, + + async execLiveYaml( + baseUrl: string, + auth: AuthContext, + sessionName: string, + yaml: string, + ): Promise { + const url = `${baseUrl}/live/${sessionName}/exec`; + try { + const res = await fetch(url, { + body: JSON.stringify({ yaml }), + headers: { + 'content-type': 'application/json', + ...auth.headers, + }, + method: 'POST', + }); + if (!res.ok) { + await this.handleApiError(res, 'Failed to execute test'); + } + + return await parseJsonResponse(res, 'Failed to execute test'); + } catch (error) { + if (error instanceof TypeError && error.message === 'fetch failed') { + throw this.enhanceFetchError(error, url); + } + + throw error; + } + }, + + async stopLiveSession( + baseUrl: string, + auth: AuthContext, + sessionName: string, + ): Promise { + const url = `${baseUrl}/live/${sessionName}`; + try { + const res = await fetch(url, { + headers: { ...auth.headers }, + method: 'DELETE', + }); + if (!res.ok) { + await this.handleApiError(res, 'Failed to stop session'); + } + } catch (error) { + if (error instanceof TypeError && error.message === 'fetch failed') { + throw this.enhanceFetchError(error, url); + } + + throw error; + } + }, + + async getLiveSession( + baseUrl: string, + auth: AuthContext, + sessionName: string, + ): Promise { + const url = `${baseUrl}/live/${sessionName}`; + try { + const res = await fetch(url, { + headers: { ...auth.headers }, + method: 'GET', + }); + if (!res.ok) { + await this.handleApiError(res, 'Failed to get session status'); + } + + return await parseJsonResponse(res, 'Failed to get session status'); + } catch (error) { + if (error instanceof TypeError && error.message === 'fetch failed') { + throw this.enhanceFetchError(error, url); + } + + throw error; + } + }, }; diff --git a/src/types/domain/live.types.ts b/src/types/domain/live.types.ts new file mode 100644 index 0000000..6917048 --- /dev/null +++ b/src/types/domain/live.types.ts @@ -0,0 +1,23 @@ +// Hand-defined: the swagger spec has no /live routes, so openapi-typescript +// cannot generate these in schema.types.ts. + +/** Summary returned when a live session is created. */ +export interface LiveSessionSummary { + id: number; + platform: string; + session_name: string; + status: string; +} + +/** Full live session record returned by the status endpoint. */ +export interface LiveSession extends LiveSessionSummary { + binary_upload_id: null | string; + created_at: string; +} + +/** Result of executing Maestro YAML against a live session. */ +export interface LiveExecResult { + error?: string; + output?: string; + success: boolean; +}