UNPKG

shipthis

Version:

ShipThis manages building and uploading your Godot games to the App Store and Google Play.

206 lines (198 loc) 7.12 kB
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 };