diff --git a/ApiClient.ts b/ApiClient.ts index 4512b22..856d7ef 100644 --- a/ApiClient.ts +++ b/ApiClient.ts @@ -1,4 +1,4 @@ -import fetch, { FetchError, fileFromSync, FormData } from 'node-fetch'; +import fetch, { fileFromSync, FormData } from 'node-fetch' export enum BenchmarkStatus { PENDING = 'PENDING', @@ -9,33 +9,57 @@ export enum BenchmarkStatus { WARNING = 'WARNING', } -export type UploadRequest = { +export type CloudUploadRequest = { benchmarkName?: string repoOwner?: string repoName?: string pullRequestId?: string - branch?: string, - commitSha?: string, - env?: { [key: string]: string }, - agent: string, - androidApiLevel?: number, - iOSVersion?: number, - includeTags: string[], - excludeTags: string[], - appBinaryId?: string, - deviceLocale?: string, + branch?: string + commitSha?: string + env?: { [key: string]: string } + agent: string + androidApiLevel?: number + iOSVersion?: number + includeTags: string[] + excludeTags: string[] + appBinaryId?: string + deviceLocale?: string +} + +export type RobinUploadRequest = { + projectId: string + repoOwner?: string + repoName?: string + pullRequestId?: string + branch?: string + commitSha?: string + env?: { [key: string]: string } + agent: string + androidApiLevel?: number + iOSVersion?: number + includeTags: string[] + excludeTags: string[] + appBinaryId?: string + deviceLocale?: string } // irrelevant data has been factored out from this model export type UploadResponse = { - uploadId: string, - teamId: string, - targetId: string, + uploadId: string + teamId: string + targetId: string + appBinaryId: string +} + +export type RobinUploadResponse = { + uploadId: string + orgId: string + appId: string appBinaryId: string } export class UploadStatusError { - constructor(public status: number, public text: string) { } + constructor(public status: number, public text: string) {} } export enum CancellationReason { @@ -46,89 +70,133 @@ export enum CancellationReason { } export type Flow = { - name: string, - status: BenchmarkStatus, - errors?: string[], + name: string + status: BenchmarkStatus + errors?: string[] cancellationReason?: CancellationReason } export type UploadStatusResponse = { - uploadId: string, - status: BenchmarkStatus, - completed: boolean, + uploadId: string + status: BenchmarkStatus + completed: boolean flows: Flow[] } export default class ApiClient { - constructor( private apiKey: string, - private apiUrl: string - ) { } + private apiUrl: string, + private projectId?: string + ) {} - async uploadRequest( - request: UploadRequest, + async cloudUploadRequest( + request: CloudUploadRequest, appFile: string | null, workspaceZip: string | null, - mappingFile: string | null, + mappingFile: string | null ): Promise { const formData = new FormData() - - formData.set('request', JSON.stringify(request)) if (appFile) { - formData.set( - 'app_binary', - fileFromSync(appFile) - ) + formData.set('app_binary', fileFromSync(appFile)) } - if (workspaceZip) { - formData.set( - 'workspace', - fileFromSync(workspaceZip) - ); + formData.set('workspace', fileFromSync(workspaceZip)) } - if (mappingFile) { - formData.set( - 'mapping', - fileFromSync(mappingFile) - ) + formData.set('mapping', fileFromSync(mappingFile)) } - + formData.set('request', JSON.stringify(request)) const res = await fetch(`${this.apiUrl}/v2/upload`, { method: 'POST', headers: { - 'Authorization': `Bearer ${this.apiKey}`, + Authorization: `Bearer ${this.apiKey}`, }, - body: formData - }); + body: formData, + }) if (!res.ok) { - const body = await res.text(); - throw new Error(`Request to ${res.url} failed (${res.status}): ${body}`); + const body = await res.text() + throw new Error(`Request to ${res.url} failed (${res.status}): ${body}`) } - return await res.json() as UploadResponse; + return (await res.json()) as UploadResponse } - async getUploadStatus( - uploadId: string, - ): Promise { - const res = await fetch(`${this.apiUrl}/v2/upload/${uploadId}/status?includeErrors=true`, { - method: 'GET', + async robinUploadRequest( + request: RobinUploadRequest, + appFile: string | null, + workspaceZip: string | null, + mappingFile: string | null + ): Promise { + const formData = new FormData() + + if (appFile) { + formData.set('app_binary', fileFromSync(appFile)) + } + if (workspaceZip) { + formData.set('workspace', fileFromSync(workspaceZip)) + } + if (mappingFile) { + formData.set('mapping', fileFromSync(mappingFile)) + } + formData.set('request', JSON.stringify(request)) + + const res = await fetch(`${this.apiUrl}/runMaestroTest`, { + method: 'POST', headers: { - 'Authorization': `Bearer ${this.apiKey}`, + Authorization: `Bearer ${this.apiKey}`, }, - }); + body: formData, + }) if (!res.ok) { - const body = await res.text(); - throw new Error(`Request to ${res.url} failed (${res.status}): ${body}`); + const body = await res.text() + throw new Error(`Request to ${res.url} failed (${res.status}): ${body}`) } + return (await res.json()) as RobinUploadResponse + } - if (res.status >= 400) { - const text = await res.text(); - Promise.reject(new UploadStatusError(res.status, text)); + async getUploadStatus(uploadId: string): Promise { + // If Project Id exist - Hit robin + if (!!this.projectId) { + const res = await fetch(`${this.apiUrl}/upload/${uploadId}`, { + method: 'GET', + headers: { + Authorization: `Bearer ${this.apiKey}`, + }, + }) + if (!res.ok) { + const body = await res.text() + throw new Error(`Request to ${res.url} failed (${res.status}): ${body}`) + } + + if (res.status >= 400) { + const text = await res.text() + Promise.reject(new UploadStatusError(res.status, text)) + } + + return (await res.json()) as UploadStatusResponse } + // Else if no project id - Hit Cloud + else { + const res = await fetch( + `${this.apiUrl}/v2/upload/${uploadId}/status?includeErrors=true`, + { + method: 'GET', + headers: { + Authorization: `Bearer ${this.apiKey}`, + }, + } + ) + if (!res.ok) { + const body = await res.text() + throw new Error(`Request to ${res.url} failed (${res.status}): ${body}`) + } - return await res.json() as UploadStatusResponse; + if (res.status >= 400) { + const text = await res.text() + Promise.reject(new UploadStatusError(res.status, text)) + } + + return (await res.json()) as UploadStatusResponse + } } } diff --git a/dist/index.js b/dist/index.js index 1c57023..dd40b67 100644 --- a/dist/index.js +++ b/dist/index.js @@ -46564,14 +46564,14 @@ var CancellationReason; CancellationReason["TIMEOUT"] = "TIMEOUT"; })(CancellationReason = exports.CancellationReason || (exports.CancellationReason = {})); class ApiClient { - constructor(apiKey, apiUrl) { + constructor(apiKey, apiUrl, projectId) { this.apiKey = apiKey; this.apiUrl = apiUrl; + this.projectId = projectId; } - uploadRequest(request, appFile, workspaceZip, mappingFile) { + cloudUploadRequest(request, appFile, workspaceZip, mappingFile) { return __awaiter(this, void 0, void 0, function* () { const formData = new node_fetch_1.FormData(); - formData.set('request', JSON.stringify(request)); if (appFile) { formData.set('app_binary', (0, node_fetch_1.fileFromSync)(appFile)); } @@ -46581,37 +46581,86 @@ class ApiClient { if (mappingFile) { formData.set('mapping', (0, node_fetch_1.fileFromSync)(mappingFile)); } + formData.set('request', JSON.stringify(request)); const res = yield (0, node_fetch_1.default)(`${this.apiUrl}/v2/upload`, { method: 'POST', headers: { - 'Authorization': `Bearer ${this.apiKey}`, + Authorization: `Bearer ${this.apiKey}`, }, - body: formData + body: formData, }); if (!res.ok) { const body = yield res.text(); throw new Error(`Request to ${res.url} failed (${res.status}): ${body}`); } - return yield res.json(); + return (yield res.json()); }); } - getUploadStatus(uploadId) { + robinUploadRequest(request, appFile, workspaceZip, mappingFile) { return __awaiter(this, void 0, void 0, function* () { - const res = yield (0, node_fetch_1.default)(`${this.apiUrl}/v2/upload/${uploadId}/status?includeErrors=true`, { - method: 'GET', + const formData = new node_fetch_1.FormData(); + if (appFile) { + formData.set('app_binary', (0, node_fetch_1.fileFromSync)(appFile)); + } + if (workspaceZip) { + formData.set('workspace', (0, node_fetch_1.fileFromSync)(workspaceZip)); + } + if (mappingFile) { + formData.set('mapping', (0, node_fetch_1.fileFromSync)(mappingFile)); + } + formData.set('request', JSON.stringify(request)); + const res = yield (0, node_fetch_1.default)(`${this.apiUrl}/runMaestroTest`, { + method: 'POST', headers: { - 'Authorization': `Bearer ${this.apiKey}`, + Authorization: `Bearer ${this.apiKey}`, }, + body: formData, }); if (!res.ok) { const body = yield res.text(); throw new Error(`Request to ${res.url} failed (${res.status}): ${body}`); } - if (res.status >= 400) { - const text = yield res.text(); - Promise.reject(new UploadStatusError(res.status, text)); + return (yield res.json()); + }); + } + getUploadStatus(uploadId) { + return __awaiter(this, void 0, void 0, function* () { + // If Project Id exist - Hit robin + if (!!this.projectId) { + const res = yield (0, node_fetch_1.default)(`${this.apiUrl}/upload/${uploadId}`, { + method: 'GET', + headers: { + Authorization: `Bearer ${this.apiKey}`, + }, + }); + if (!res.ok) { + const body = yield res.text(); + throw new Error(`Request to ${res.url} failed (${res.status}): ${body}`); + } + if (res.status >= 400) { + const text = yield res.text(); + Promise.reject(new UploadStatusError(res.status, text)); + } + return (yield res.json()); + } + // Else if no project id - Hit Cloud + else { + const res = yield (0, node_fetch_1.default)(`${this.apiUrl}/v2/upload/${uploadId}/status?includeErrors=true`, { + method: 'GET', + headers: { + Authorization: `Bearer ${this.apiKey}`, + }, + }); + if (!res.ok) { + const body = yield res.text(); + throw new Error(`Request to ${res.url} failed (${res.status}): ${body}`); + } + if (res.status >= 400) { + const text = yield res.text(); + Promise.reject(new UploadStatusError(res.status, text)); + } + return (yield res.json()); } - return yield res.json(); }); } } @@ -47007,7 +47056,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.getConsoleUrl = void 0; const core = __importStar(__nccwpck_require__(2186)); const ApiClient_1 = __importDefault(__nccwpck_require__(9494)); const app_file_1 = __nccwpck_require__(9617); @@ -47019,7 +47067,7 @@ const log_1 = __nccwpck_require__(3826); const knownAppTypes = ['ANDROID_APK', 'IOS_BUNDLE']; const listFilesInDirectory = () => { const files = (0, fs_1.readdirSync)('.', { withFileTypes: true }); - console.log("Directory contents:"); + console.log('Directory contents:'); for (const f of files) { console.log(f.isDirectory() ? `${f.name}/` : f.name); } @@ -47029,15 +47077,15 @@ const createWorkspaceZip = (workspaceFolder) => __awaiter(void 0, void 0, void 0 if (resolvedWorkspaceFolder === null || (workspaceFolder === null || workspaceFolder === void 0 ? void 0 : workspaceFolder.length) === 0) { if ((0, fs_1.existsSync)('.maestro')) { resolvedWorkspaceFolder = '.maestro'; - (0, log_1.info)("Packaging .maestro folder"); + (0, log_1.info)('Packaging .maestro folder'); } else if ((0, fs_1.existsSync)('.mobiledev')) { resolvedWorkspaceFolder = '.mobiledev'; - (0, log_1.info)("Packaging .mobiledev folder"); + (0, log_1.info)('Packaging .mobiledev folder'); } else { listFilesInDirectory(); - throw new Error("Default workspace directory does not exist: .maestro/"); + throw new Error('Default workspace directory does not exist: .maestro/'); } } else if (!(0, fs_1.existsSync)(resolvedWorkspaceFolder)) { @@ -47046,46 +47094,76 @@ const createWorkspaceZip = (workspaceFolder) => __awaiter(void 0, void 0, void 0 yield (0, archive_utils_1.zipFolder)(resolvedWorkspaceFolder, 'workspace.zip'); return 'workspace.zip'; }); -const getConsoleUrl = (uploadId, teamId, appId) => { - return `https://console.mobile.dev/uploads/${uploadId}?teamId=${teamId}&appId=${appId}`; -}; -exports.getConsoleUrl = getConsoleUrl; const run = () => __awaiter(void 0, void 0, void 0, function* () { - const { apiKey, apiUrl, name, appFilePath, mappingFile, workspaceFolder, branchName, commitSha, repoOwner, repoName, pullRequestId, env, async, androidApiLevel, iOSVersion, includeTags, excludeTags, appBinaryId, deviceLocale, timeout, } = yield (0, params_1.getParameters)(); + const { apiKey, apiUrl, name, appFilePath, mappingFile, workspaceFolder, branchName, commitSha, repoOwner, repoName, pullRequestId, env, async, androidApiLevel, iOSVersion, includeTags, excludeTags, appBinaryId, deviceLocale, timeout, projectId, } = yield (0, params_1.getParameters)(); let appFile = null; - if (appFilePath !== "") { + if (appFilePath !== '') { appFile = yield (0, app_file_1.validateAppFile)(yield (0, archive_utils_1.zipIfFolder)(appFilePath)); if (!knownAppTypes.includes(appFile.type)) { throw new Error(`Unsupported app file type: ${appFile.type}`); } } const workspaceZip = yield createWorkspaceZip(workspaceFolder); - const client = new ApiClient_1.default(apiKey, apiUrl); - (0, log_1.info)("Uploading to Maestro Cloud"); - const request = { - benchmarkName: name, - branch: branchName, - commitSha: commitSha, - repoOwner: repoOwner, - repoName: repoName, - pullRequestId: pullRequestId, - env: env, - agent: 'github', - androidApiLevel: androidApiLevel, - iOSVersion: iOSVersion, - includeTags: includeTags, - excludeTags: excludeTags, - appBinaryId: appBinaryId || undefined, - deviceLocale: deviceLocale || undefined, - }; - const { uploadId, teamId, targetId: appId, appBinaryId: uploadedBinaryId } = yield client.uploadRequest(request, appFile && appFile.path, workspaceZip, mappingFile && (yield (0, archive_utils_1.zipIfFolder)(mappingFile))); - const consoleUrl = (0, exports.getConsoleUrl)(uploadId, teamId, appId); - (0, log_1.info)(`Visit the web console for more details about the upload: ${consoleUrl}\n`); - core.setOutput('MAESTRO_CLOUD_CONSOLE_URL', consoleUrl); - core.setOutput('MAESTRO_CLOUD_APP_BINARY_ID', uploadedBinaryId); - !async && new StatusPoller_1.default(client, uploadId, consoleUrl).startPolling(timeout); + const client = new ApiClient_1.default(apiKey, apiUrl, projectId); + if (!!projectId) { + /** + * If project Exist - Its Robin + */ + (0, log_1.info)('Uploading to Robin Basic'); + const request = { + projectId: projectId, + repoOwner: repoOwner, + repoName: repoName, + agent: 'github', + branch: branchName, + commitSha: commitSha, + pullRequestId: pullRequestId, + env: env, + androidApiLevel: androidApiLevel, + iOSVersion: iOSVersion, + includeTags: includeTags, + excludeTags: excludeTags, + appBinaryId: appBinaryId || undefined, + deviceLocale: deviceLocale || undefined, + }; + const { uploadId, orgId, appId, appBinaryId: appBinaryIdResponse, } = yield client.robinUploadRequest(request, appFile && appFile.path, workspaceZip, mappingFile && (yield (0, archive_utils_1.zipIfFolder)(mappingFile))); + const consoleUrl = `https://copilot.mobile.dev/project/${projectId}/maestro-test/app/${appId}/upload/${uploadId}`; + core.setOutput('ROBIN_CONSOLE_URL', consoleUrl); + core.setOutput('ROBIN_APP_BINARY_ID', appBinaryIdResponse); + !async && + new StatusPoller_1.default(client, uploadId, consoleUrl).startPolling(timeout); + } + else { + /** + * If project Exist - Its Cloud + */ + (0, log_1.info)('Uploading to Maestro Cloud'); + const request = { + benchmarkName: name, + repoOwner: repoOwner, + repoName: repoName, + agent: 'github', + branch: branchName, + commitSha: commitSha, + pullRequestId: pullRequestId, + env: env, + androidApiLevel: androidApiLevel, + iOSVersion: iOSVersion, + includeTags: includeTags, + excludeTags: excludeTags, + appBinaryId: appBinaryId || undefined, + deviceLocale: deviceLocale || undefined, + }; + const { uploadId, teamId, appBinaryId: appBinaryIdResponse, } = yield client.cloudUploadRequest(request, appFile && appFile.path, workspaceZip, mappingFile && (yield (0, archive_utils_1.zipIfFolder)(mappingFile))); + const consoleUrl = `https://console.mobile.dev/uploads/${uploadId}?teamId=${teamId}&appId=${appBinaryIdResponse}`; + (0, log_1.info)(`Visit the web console for more details about the upload: ${consoleUrl}\n`); + core.setOutput('MAESTRO_CLOUD_CONSOLE_URL', consoleUrl); + core.setOutput('MAESTRO_CLOUD_APP_BINARY_ID', appBinaryId); + !async && + new StatusPoller_1.default(client, uploadId, consoleUrl).startPolling(timeout); + } }); -run().catch(e => { +run().catch((e) => { core.setFailed(`Error running Maestro Cloud Upload Action: ${e.message}`); }); @@ -47235,37 +47313,43 @@ function parseTags(tags) { if (tags === undefined || tags === '') return []; if (tags.includes(',')) { - const arrayTags = tags.split(',') - .map(it => it.trim()); + const arrayTags = tags.split(',').map((it) => it.trim()); if (!Array.isArray(arrayTags)) - throw new Error("tags must be an Array."); + throw new Error('tags must be an Array.'); return arrayTags; } return [tags]; } function getParameters() { return __awaiter(this, void 0, void 0, function* () { - const apiUrl = core.getInput('api-url', { required: false }) || 'https://api.mobile.dev'; + const projectId = core.getInput('project-id', { required: false }) || undefined; + const apiUrl = core.getInput('api-url', { required: false }) || + (projectId + ? `https://api.copilot.mobile.dev/v2/project/${projectId}` + : 'https://api.mobile.dev'); const name = core.getInput('name', { required: false }) || getInferredName(); const apiKey = core.getInput('api-key', { required: true }); const mappingFileInput = core.getInput('mapping-file', { required: false }); const workspaceFolder = core.getInput('workspace', { required: false }); const mappingFile = mappingFileInput && (0, app_file_1.validateMappingFile)(mappingFileInput); const async = core.getInput('async', { required: false }) === 'true'; - const androidApiLevelString = core.getInput('android-api-level', { required: false }); + const androidApiLevelString = core.getInput('android-api-level', { + required: false, + }); const iOSVersionString = core.getInput('ios-version', { required: false }); const includeTags = parseTags(core.getInput('include-tags', { required: false })); const excludeTags = parseTags(core.getInput('exclude-tags', { required: false })); const appFilePath = core.getInput('app-file', { required: false }); const appBinaryId = core.getInput('app-binary-id', { required: false }); - if (!(appFilePath !== "") !== (appBinaryId !== "")) { - throw new Error("Either app-file or app-binary-id must be used"); + if (!(appFilePath !== '') !== (appBinaryId !== '')) { + throw new Error('Either app-file or app-binary-id must be used'); } const deviceLocale = core.getInput('device-locale', { required: false }); const timeoutString = core.getInput('timeout', { required: false }); var env = {}; - env = core.getMultilineInput('env', { required: false }) - .map(it => { + env = core + .getMultilineInput('env', { required: false }) + .map((it) => { const parts = it.split('='); if (parts.length < 2) { throw new Error(`Invalid env parameter: ${it}`); @@ -47305,6 +47389,7 @@ function getParameters() { appBinaryId, deviceLocale, timeout, + projectId, }; }); } diff --git a/index.ts b/index.ts index 82ff16b..4519169 100644 --- a/index.ts +++ b/index.ts @@ -1,47 +1,52 @@ import * as core from '@actions/core' -import ApiClient, { UploadRequest } from './ApiClient' -import { validateAppFile } from './app_file'; -import { zipFolder, zipIfFolder } from './archive_utils'; -import { getParameters } from './params'; -import { existsSync, readdirSync } from 'fs'; -import StatusPoller from './StatusPoller'; -import { info } from './log'; +import ApiClient, { + CloudUploadRequest, + UploadResponse, + RobinUploadRequest, + RobinUploadResponse, +} from './ApiClient' +import { validateAppFile } from './app_file' +import { zipFolder, zipIfFolder } from './archive_utils' +import { getParameters } from './params' +import { existsSync, readdirSync } from 'fs' +import StatusPoller from './StatusPoller' +import { info } from './log' const knownAppTypes = ['ANDROID_APK', 'IOS_BUNDLE'] const listFilesInDirectory = () => { const files = readdirSync('.', { withFileTypes: true }) - console.log("Directory contents:") + console.log('Directory contents:') for (const f of files) { console.log(f.isDirectory() ? `${f.name}/` : f.name) } } -const createWorkspaceZip = async (workspaceFolder: string | null): Promise => { +const createWorkspaceZip = async ( + workspaceFolder: string | null +): Promise => { let resolvedWorkspaceFolder = workspaceFolder if (resolvedWorkspaceFolder === null || workspaceFolder?.length === 0) { if (existsSync('.maestro')) { resolvedWorkspaceFolder = '.maestro' - info("Packaging .maestro folder") + info('Packaging .maestro folder') } else if (existsSync('.mobiledev')) { resolvedWorkspaceFolder = '.mobiledev' - info("Packaging .mobiledev folder") + info('Packaging .mobiledev folder') } else { listFilesInDirectory() - throw new Error("Default workspace directory does not exist: .maestro/") + throw new Error('Default workspace directory does not exist: .maestro/') } } else if (!existsSync(resolvedWorkspaceFolder)) { - throw new Error(`Workspace directory does not exist: ${resolvedWorkspaceFolder}`) + throw new Error( + `Workspace directory does not exist: ${resolvedWorkspaceFolder}` + ) } - await zipFolder(resolvedWorkspaceFolder, 'workspace.zip'); + await zipFolder(resolvedWorkspaceFolder, 'workspace.zip') return 'workspace.zip' } -export const getConsoleUrl = (uploadId: string, teamId: string, appId: string): string => { - return `https://console.mobile.dev/uploads/${uploadId}?teamId=${teamId}&appId=${appId}` -} - const run = async () => { const { apiKey, @@ -64,13 +69,12 @@ const run = async () => { appBinaryId, deviceLocale, timeout, + projectId, } = await getParameters() let appFile = null - if (appFilePath !== "") { - appFile = await validateAppFile( - await zipIfFolder(appFilePath) - ); + if (appFilePath !== '') { + appFile = await validateAppFile(await zipIfFolder(appFilePath)) if (!knownAppTypes.includes(appFile.type)) { throw new Error(`Unsupported app file type: ${appFile.type}`) } @@ -78,40 +82,87 @@ const run = async () => { const workspaceZip = await createWorkspaceZip(workspaceFolder) - const client = new ApiClient(apiKey, apiUrl) + const client = new ApiClient(apiKey, apiUrl, projectId) - info("Uploading to Maestro Cloud") - const request: UploadRequest = { - benchmarkName: name, - branch: branchName, - commitSha: commitSha, - repoOwner: repoOwner, - repoName: repoName, - pullRequestId: pullRequestId, - env: env, - agent: 'github', - androidApiLevel: androidApiLevel, - iOSVersion: iOSVersion, - includeTags: includeTags, - excludeTags: excludeTags, - appBinaryId: appBinaryId || undefined, - deviceLocale: deviceLocale || undefined, + if (!!projectId) { + /** + * If project Exist - Its Robin + */ + info('Uploading to Robin Basic') + const request: RobinUploadRequest = { + projectId: projectId, + repoOwner: repoOwner, + repoName: repoName, + agent: 'github', + branch: branchName, + commitSha: commitSha, + pullRequestId: pullRequestId, + env: env, + androidApiLevel: androidApiLevel, + iOSVersion: iOSVersion, + includeTags: includeTags, + excludeTags: excludeTags, + appBinaryId: appBinaryId || undefined, + deviceLocale: deviceLocale || undefined, + } + const { + uploadId, + orgId, + appId, + appBinaryId: appBinaryIdResponse, + }: RobinUploadResponse = await client.robinUploadRequest( + request, + appFile && appFile.path, + workspaceZip, + mappingFile && (await zipIfFolder(mappingFile)) + ) + const consoleUrl = `https://copilot.mobile.dev/project/${projectId}/maestro-test/app/${appId}/upload/${uploadId}` + core.setOutput('ROBIN_CONSOLE_URL', consoleUrl) + core.setOutput('ROBIN_APP_BINARY_ID', appBinaryIdResponse) + !async && + new StatusPoller(client, uploadId, consoleUrl).startPolling(timeout) + } else { + /** + * If project Exist - Its Cloud + */ + info('Uploading to Maestro Cloud') + const request: CloudUploadRequest = { + benchmarkName: name, + repoOwner: repoOwner, + repoName: repoName, + agent: 'github', + branch: branchName, + commitSha: commitSha, + pullRequestId: pullRequestId, + env: env, + androidApiLevel: androidApiLevel, + iOSVersion: iOSVersion, + includeTags: includeTags, + excludeTags: excludeTags, + appBinaryId: appBinaryId || undefined, + deviceLocale: deviceLocale || undefined, + } + const { + uploadId, + teamId, + appBinaryId: appBinaryIdResponse, + }: UploadResponse = await client.cloudUploadRequest( + request, + appFile && appFile.path, + workspaceZip, + mappingFile && (await zipIfFolder(mappingFile)) + ) + const consoleUrl = `https://console.mobile.dev/uploads/${uploadId}?teamId=${teamId}&appId=${appBinaryIdResponse}` + info( + `Visit the web console for more details about the upload: ${consoleUrl}\n` + ) + core.setOutput('MAESTRO_CLOUD_CONSOLE_URL', consoleUrl) + core.setOutput('MAESTRO_CLOUD_APP_BINARY_ID', appBinaryId) + !async && + new StatusPoller(client, uploadId, consoleUrl).startPolling(timeout) } - - const { uploadId, teamId, targetId: appId, appBinaryId: uploadedBinaryId } = await client.uploadRequest( - request, - appFile && appFile.path, - workspaceZip, - mappingFile && await zipIfFolder(mappingFile), - ) - const consoleUrl = getConsoleUrl(uploadId, teamId, appId) - info(`Visit the web console for more details about the upload: ${consoleUrl}\n`) - core.setOutput('MAESTRO_CLOUD_CONSOLE_URL', consoleUrl) - core.setOutput('MAESTRO_CLOUD_APP_BINARY_ID', uploadedBinaryId) - - !async && new StatusPoller(client, uploadId, consoleUrl).startPolling(timeout) } -run().catch(e => { +run().catch((e) => { core.setFailed(`Error running Maestro Cloud Upload Action: ${e.message}`) }) diff --git a/params.ts b/params.ts index edf55d5..03ccb83 100644 --- a/params.ts +++ b/params.ts @@ -1,44 +1,51 @@ -import * as github from '@actions/github'; -import * as core from '@actions/core'; -import { AppFile, validateMappingFile } from './app_file'; +import * as github from '@actions/github' +import * as core from '@actions/core' +import { AppFile, validateMappingFile } from './app_file' import { PushEvent } from '@octokit/webhooks-definitions/schema' export type Params = { - apiKey: string, - apiUrl: string, - name: string, - appFilePath: string, - mappingFile: string | null, - workspaceFolder: string | null, + projectId?: string + apiKey: string + apiUrl: string + name: string + appFilePath: string + mappingFile: string | null + workspaceFolder: string | null branchName: string commitSha?: string repoName: string repoOwner: string - pullRequestId?: string, - env?: { [key: string]: string }, - async?: boolean, - androidApiLevel?: number, - iOSVersion?: number, - includeTags: string[], - excludeTags: string[], - appBinaryId: string, - deviceLocale?: string, - timeout?: number, + pullRequestId?: string + env?: { [key: string]: string } + async?: boolean + androidApiLevel?: number + iOSVersion?: number + includeTags: string[] + excludeTags: string[] + appBinaryId: string + deviceLocale?: string + timeout?: number } function getBranchName(): string { const pullRequest = github.context.payload.pull_request if (pullRequest) { - const branchName = pullRequest?.head?.ref; + const branchName = pullRequest?.head?.ref if (!branchName) { - throw new Error(`Unable find pull request ref: ${JSON.stringify(pullRequest, undefined, 2)}`) + throw new Error( + `Unable find pull request ref: ${JSON.stringify( + pullRequest, + undefined, + 2 + )}` + ) } return branchName } const regex = /refs\/(heads|tags)\/(.*)/ const ref = github.context.ref - let result = regex.exec(ref); + let result = regex.exec(ref) if (!result || result.length < 3) { throw new Error(`Failed to parse GitHub ref: ${ref}`) } @@ -71,11 +78,11 @@ function getPullRequestTitle(): string | undefined { function getInferredName(): string { const pullRequestTitle = getPullRequestTitle() - if (pullRequestTitle) return pullRequestTitle; + if (pullRequestTitle) return pullRequestTitle if (github.context.eventName === 'push') { const pushPayload = github.context.payload as PushEvent - const commitMessage = pushPayload.head_commit?.message; + const commitMessage = pushPayload.head_commit?.message if (commitMessage) return commitMessage } @@ -98,11 +105,9 @@ function parseTags(tags?: string): string[] { if (tags === undefined || tags === '') return [] if (tags.includes(',')) { - const arrayTags = tags.split(',') - .map(it => it.trim()) + const arrayTags = tags.split(',').map((it) => it.trim()) - if (!Array.isArray(arrayTags)) - throw new Error("tags must be an Array.") + if (!Array.isArray(arrayTags)) throw new Error('tags must be an Array.') return arrayTags } @@ -111,30 +116,43 @@ function parseTags(tags?: string): string[] { } export async function getParameters(): Promise { - const apiUrl = core.getInput('api-url', { required: false }) || 'https://api.mobile.dev' + const projectId = + core.getInput('project-id', { required: false }) || undefined + const apiUrl = + core.getInput('api-url', { required: false }) || + (projectId + ? `https://api.copilot.mobile.dev/v2/project/${projectId}` + : 'https://api.mobile.dev') const name = core.getInput('name', { required: false }) || getInferredName() const apiKey = core.getInput('api-key', { required: true }) const mappingFileInput = core.getInput('mapping-file', { required: false }) const workspaceFolder = core.getInput('workspace', { required: false }) const mappingFile = mappingFileInput && validateMappingFile(mappingFileInput) const async = core.getInput('async', { required: false }) === 'true' - const androidApiLevelString = core.getInput('android-api-level', { required: false }) + const androidApiLevelString = core.getInput('android-api-level', { + required: false, + }) const iOSVersionString = core.getInput('ios-version', { required: false }) - const includeTags = parseTags(core.getInput('include-tags', { required: false })) - const excludeTags = parseTags(core.getInput('exclude-tags', { required: false })) + const includeTags = parseTags( + core.getInput('include-tags', { required: false }) + ) + const excludeTags = parseTags( + core.getInput('exclude-tags', { required: false }) + ) const appFilePath = core.getInput('app-file', { required: false }) const appBinaryId = core.getInput('app-binary-id', { required: false }) - if (!(appFilePath !== "") !== (appBinaryId !== "")) { - throw new Error("Either app-file or app-binary-id must be used") + if (!(appFilePath !== '') !== (appBinaryId !== '')) { + throw new Error('Either app-file or app-binary-id must be used') } const deviceLocale = core.getInput('device-locale', { required: false }) const timeoutString = core.getInput('timeout', { required: false }) var env: { [key: string]: string } = {} - env = core.getMultilineInput('env', { required: false }) - .map(it => { + env = core + .getMultilineInput('env', { required: false }) + .map((it) => { const parts = it.split('=') if (parts.length < 2) { @@ -178,5 +196,6 @@ export async function getParameters(): Promise { appBinaryId, deviceLocale, timeout, + projectId, } -} \ No newline at end of file +}