Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 7 additions & 73 deletions src/commands/live.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`);
Expand Down Expand Up @@ -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`);
},
Expand All @@ -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
Expand Down Expand Up @@ -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`);
},
Expand All @@ -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)}`);
Expand Down
142 changes: 142 additions & 0 deletions src/gateways/api-gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand Down Expand Up @@ -574,4 +579,141 @@ export const ApiGateway = {
throw error;
}
},

async startLiveSession(
baseUrl: string,
auth: AuthContext,
params: { binaryUploadId?: string; platform: string },
): Promise<LiveSessionSummary> {
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<LiveSessionSummary>(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<void> {
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<LiveExecResult> {
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<LiveExecResult>(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<void> {
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<LiveSession> {
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<LiveSession>(res, 'Failed to get session status');
} catch (error) {
if (error instanceof TypeError && error.message === 'fetch failed') {
throw this.enhanceFetchError(error, url);
}

throw error;
}
},
};
23 changes: 23 additions & 0 deletions src/types/domain/live.types.ts
Original file line number Diff line number Diff line change
@@ -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;
}
Loading