shipthis
Version:
ShipThis manages building and uploading your Godot games to the App Store and Google Play.
206 lines (198 loc) • 7.12 kB
JavaScript
import { jsx, jsxs } from 'react/jsx-runtime';
import { measureElement, Box, Text } from 'ink';
import Spinner from 'ink-spinner';
import { useInfiniteQuery } from '@tanstack/react-query';
import axios from 'axios';
import { useState, useRef, useEffect } from 'react';
import 'node:fs';
import 'crypto-js';
import 'uuid';
import { p as getAuthedHeaders, o as API_URL, I as castArrayObjectDates, J as JobStatus, a9 as castJobDates, a7 as castObjectDates, aa as getShortTime } from './index-BwnzoldS.js';
import { d as getStageColor, j as getMessageColor } from './index-CJWMt1s-.js';
import 'luxon';
import 'fast-glob';
import 'yazl';
import 'socket.io-client';
import { c as cacheKeys } from './useAndroidServiceAccountTestResult-CwKeW0ED.js';
import 'node:crypto';
import 'node:path';
import 'node:readline';
import 'node:url';
import 'readline-sync';
import 'isomorphic-git';
import '@oclif/core';
import 'fullscreen-ink';
import { f as useJob } from './index-hoHfGrjg.js';
import { u as useWebSocket } from './useWebSocket-cM5yOcDv.js';
import { T as Title } from './Title-BCQtayg6.js';
import stringLength from 'string-length';
import stripAnsi from 'strip-ansi';
function arrayToDictionary(array, key = "id") {
return array.reduce((a, i) => {
a[i[key]] = i;
return a;
}, {});
}
function dictionaryToArray(dictionary) {
return Object.keys(dictionary).map((key) => dictionary[key]);
}
async function queryJobLogs({
cursor,
jobId,
pageSize = 10,
projectId
}) {
try {
const headers = getAuthedHeaders();
const base = `${API_URL}/projects/${projectId}/jobs/${jobId}/logs/?pageSize=${pageSize}`;
const url = base + (cursor ? `&cursor=${cursor}` : "");
const response = await axios.get(url, { headers });
return {
...response.data,
data: castArrayObjectDates(response.data.data, ["sentAt", "createdAt"])
};
} catch (error) {
console.warn("queryJobLogs Error", error);
throw error;
}
}
const useJobLogs = (props) => {
const queryResult = useInfiniteQuery({
getNextPageParam: (lastPage) => lastPage.nextCursor,
initialPageParam: props.cursor,
async queryFn({ pageParam }) {
return queryJobLogs({
...props,
cursor: pageParam
});
},
queryKey: cacheKeys.jobLogs(props)
});
return queryResult;
};
function useJobWatching({
isWatching,
jobId,
onComplete,
onFailure,
onJobUpdate,
onNewLogEntry,
projectId
}) {
const [websocketJob, setWebsocketJob] = useState(null);
const [mostRecentLog, setMostRecentLog] = useState(null);
const prevJobStatus = useRef(JobStatus.PENDING);
const handleJobUpdate = (job2) => {
const completed = /* @__PURE__ */ new Set([JobStatus.COMPLETED, JobStatus.FAILED]);
const wasRunning = !completed.has(prevJobStatus.current);
if (completed.has(job2.status) && wasRunning) {
if (job2.status === JobStatus.FAILED) {
onFailure && onFailure(job2);
} else {
onComplete && onComplete(job2);
}
}
prevJobStatus.current = job2.status;
};
const jobStatusListener = {
async eventHandler(pattern, rawJob) {
if (rawJob.id !== jobId) return;
const job2 = castJobDates(rawJob);
setWebsocketJob(job2);
handleJobUpdate(job2);
if (onJobUpdate) onJobUpdate(job2);
},
getPattern: () => [`project.${projectId}:job:created`, `project.${projectId}:job:updated`]
};
const jobProgressListener = {
async eventHandler(pattern, rawLogEntry) {
const logEntry = castObjectDates(rawLogEntry, ["sentAt", "createdAt"]);
if (onNewLogEntry) onNewLogEntry(logEntry);
setMostRecentLog(logEntry);
},
getPattern: () => `project.${projectId}:job.${jobId}:log`
};
useWebSocket(isWatching ? [jobStatusListener, jobProgressListener] : []);
const { data: job, isLoading } = useJob({
jobId,
projectId
});
useEffect(() => {
setWebsocketJob(null);
}, [jobId, projectId, isWatching, job]);
const fetchedJob = job ? job : null;
const data = websocketJob ? websocketJob : fetchedJob;
const progress = mostRecentLog?.progress || null;
const stage = mostRecentLog?.stage || null;
return {
data,
isLoading,
progress,
stage
};
}
function getSortedJobLogs(logs) {
return logs.sort((a, b) => a.sentAt.toMillis() - b.sentAt.toMillis());
}
function useJobLogTail({ isWatching, jobId, length, projectId }) {
const [websocketLogs, setWebsocketLogs] = useState([]);
useJobWatching({
isWatching,
jobId,
onNewLogEntry(logEntry) {
setWebsocketLogs((prevLogs) => [...prevLogs, logEntry]);
},
projectId
});
const { data: fetchedJobLogs, isLoading } = useJobLogs({
jobId,
pageSize: length,
projectId
});
useEffect(() => {
setWebsocketLogs([]);
}, [jobId, projectId, length, isWatching, fetchedJobLogs]);
const firstPage = fetchedJobLogs ? fetchedJobLogs?.pages[0].data : [];
const allLogs = [...firstPage, ...websocketLogs];
const allLogsById = arrayToDictionary(allLogs);
const uniqueLogs = dictionaryToArray(allLogsById);
const data = getSortedJobLogs(uniqueLogs).slice(-length);
return {
data,
isLoading
};
}
const TruncatedText = ({ children, wrap, ...textPropsWithoutWrap }) => {
const ref = useRef();
const [width, setWidth] = useState(null);
useEffect(() => {
if (!ref.current) return;
const { width: measuredWidth } = measureElement(ref.current);
setWidth(measuredWidth);
}, []);
const getTruncated = (input) => {
const withoutCrlf = input.replaceAll(/[\n\r]/g, "");
if (width === null) return withoutCrlf;
const withoutAnsi = stripAnsi(withoutCrlf);
const textLength = stringLength(withoutAnsi);
return textLength > width ? withoutCrlf.slice(0, Math.max(0, width)) : withoutCrlf;
};
return /* @__PURE__ */ jsx(Box, { ref, children: /* @__PURE__ */ jsx(Text, { ...textPropsWithoutWrap, children: getTruncated(children) + "\x1B[0m" }) });
};
const JobLogTail = (props) => {
const { data, isLoading } = useJobLogTail(props);
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
/* @__PURE__ */ jsx(Title, { children: "Job Logs" }),
isLoading && /* @__PURE__ */ jsx(Spinner, { type: "dots" }),
/* @__PURE__ */ jsx(Box, { flexDirection: "column", children: data.map((log) => {
const stageColor = getStageColor(log.stage);
const messageColor = getMessageColor(log.level);
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", height: 1, overflow: "hidden", children: [
/* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { children: getShortTime(log.sentAt) }) }),
/* @__PURE__ */ jsx(Box, { justifyContent: "flex-start", marginLeft: 1, width: 9, children: /* @__PURE__ */ jsx(Text, { color: stageColor, children: log.stage }) }),
/* @__PURE__ */ jsx(Box, { height: 1, marginLeft: 1, marginRight: 2, overflow: "hidden", children: /* @__PURE__ */ jsx(TruncatedText, { color: messageColor, children: log.message }) })
] }, log.id);
}) })
] });
};
export { JobLogTail as J, useJobWatching as u };