shipthis
Version:
ShipThis manages building and uploading your Godot games to the App Store and Google Play.
222 lines (213 loc) • 8.73 kB
JavaScript
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
import { useStdin, useInput, Text, Box } from 'ink';
import Spinner from 'ink-spinner';
import open from 'open';
import React, { useState, useEffect, useContext, useRef } from 'react';
import { P as Platform, ab as getShortDateTime, p as getAuthedHeaders, o as API_URL, I as castArrayObjectDates, J as JobStatus, ac as getShortTimeDelta, z as getJob, E as getProject, a6 as getShortAuthRequiredUrl, K as queryClient, ad as BuildType, W as WEB_URL } from './index-BwnzoldS.js';
import { g as getShortUUID, h as getPlatformName, s as scriptDir } from './index-CJWMt1s-.js';
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';
import { c as cacheKeys, u as useAndroidServiceAccountTestResult, K as KeyTestStatus, a as KeyTestError } from './useAndroidServiceAccountTestResult-CwKeW0ED.js';
import 'luxon';
import fs__default from 'node:fs';
import 'fast-glob';
import 'uuid';
import 'yazl';
import 'socket.io-client';
import 'fullscreen-ink';
import 'string-length';
import 'strip-ansi';
import '@inkjs/ui';
import path from 'node:path';
import { e as ejs } from './ejs-DirFZbza.js';
import { setOptions, parse } from 'marked';
import TerminalRenderer from 'marked-terminal';
import 'qrcode';
async function queryBuilds({ projectId, ...pageAndSortParams }) {
try {
const headers = getAuthedHeaders();
const url = `${API_URL}/projects/${projectId}/builds`;
const response = await axios.get(url, { headers, params: pageAndSortParams });
return {
...response.data,
data: castArrayObjectDates(response.data.data)
};
} catch (error) {
console.warn("queryBuilds Error", error);
throw error;
}
}
function getBuildSummary(build) {
const ext = build.buildType || (build.platform === Platform.IOS ? "IPA" : "AAB");
const filename = `game.${ext.toLowerCase()}`;
const details = getJobDetailsSummary(build.jobDetails);
const summary = {};
summary.id = getShortUUID(build.id);
summary.version = details.version;
summary.gitInfo = details.gitInfo;
summary.platform = getPlatformName(build.platform);
summary.jobId = getShortUUID(build.jobId);
summary.createdAt = getShortDateTime(build.createdAt);
summary.cmd = `shipthis game build download ${getShortUUID(build.id)} ${filename}`;
return summary;
}
const useBuilds = (props) => {
const queryResult = useQuery({
queryFn: async () => queryBuilds(props),
queryKey: cacheKeys.builds(props)
});
return queryResult;
};
function getJobDetailsSummary(jobDetails) {
const semanticVersion = jobDetails?.semanticVersion || "N/A";
const buildNumber = jobDetails?.buildNumber || "N/A";
const gitCommit = jobDetails?.gitCommitHash ? getShortUUID(jobDetails.gitCommitHash) : "";
const gitBranch = jobDetails?.gitBranch || "";
const details = {};
details.version = `${semanticVersion} (${buildNumber})`;
details.gitInfo = gitCommit ? `${gitCommit} (${gitBranch})` : "";
return details;
}
function getJobSummary(job, timeNow) {
const inProgress = ![JobStatus.COMPLETED, JobStatus.FAILED].includes(job.status);
const details = getJobDetailsSummary(job.details);
const summary = {};
summary.id = getShortUUID(job.id);
summary.version = details.version;
summary.gitInfo = details.gitInfo;
summary.platform = getPlatformName(job.type);
summary.status = job.status;
summary.createdAt = getShortDateTime(job.createdAt);
summary.runtime = getShortTimeDelta(job.createdAt, inProgress ? timeNow : job.updatedAt);
return summary;
}
const useJob = (props) => useQuery({
queryFn: () => getJob(props.jobId, props.projectId),
queryKey: cacheKeys.job(props)
});
const useSafeInput = (handler, options = { isActive: true }) => {
const { isRawModeSupported } = useStdin();
const isActive = isRawModeSupported === true && options.isActive !== false;
useInput(
(input, key) => {
const lowerInput = input.toLowerCase();
return handler(lowerInput, key);
},
{ isActive }
);
};
const cleanHyperlinks = (input) => (
// When we run in a <ScrollArea> the links break
// Remove OSC 8 hyperlink wrappers but preserve the styled content inside
input.replaceAll(/\u001B]8;;[^\u0007]*\u0007/g, "").replaceAll("\x1B]8;;\x07", "")
);
const getRenderedMarkdown = ({ filename, templateVars, ...options }) => {
setOptions({
renderer: new TerminalRenderer({
...options
})
});
const entrypointPath = fs__default.realpathSync(process.argv[1]);
const root = path.dirname(entrypointPath);
const mdPath = path.join(root, "..", "assets", "markdown", filename);
const mdTemplate = fs__default.readFileSync(mdPath, "utf8").trim();
const markdown = ejs.render(mdTemplate, templateVars ?? {}, {
filename: mdPath
});
const rendered = parse(markdown).trim();
const cleaned = cleanHyperlinks(rendered);
return cleaned;
};
const Markdown = ({ filename, templateVars, ...options }) => {
const [text, setText] = useState("");
useEffect(() => {
const cleaned = getRenderedMarkdown({ filename, templateVars, ...options });
setText(cleaned);
}, [filename, templateVars, options]);
return /* @__PURE__ */ jsx(Text, { children: text });
};
const CommandContext = React.createContext({
command: null,
setCommand(command) {
}
});
const CommandProvider = (props) => {
const [command, setCommand] = useState(props.command || null);
return /* @__PURE__ */ jsx(CommandContext.Provider, { value: { command, setCommand }, children: props.children });
};
const GameContext = React.createContext({
game: null,
gameId: null,
setGameId(gameId) {
}
});
const GameProvider = ({ children }) => {
const { command } = React.useContext(CommandContext);
const [gameId, setGameId] = useState(command?.getGameId() || null);
const [game, setGame] = useState(null);
const handleGameIdChange = async () => {
if (!gameId) {
setGame(null);
return;
}
const game2 = await getProject(gameId);
setGame(game2);
};
useEffect(() => {
handleGameIdChange();
}, [gameId]);
return /* @__PURE__ */ jsx(GameContext.Provider, { value: { game, gameId, setGameId }, children });
};
scriptDir(import.meta);
const getIsAppFound = (result) => {
const isFound = result?.status === KeyTestStatus.SUCCESS || result?.status === KeyTestStatus.ERROR && result?.error === KeyTestError.NOT_INVITED;
return isFound;
};
const CreateGooglePlayGame = (props) => {
const { gameId } = useContext(GameContext);
return /* @__PURE__ */ jsx(Fragment, { children: gameId && /* @__PURE__ */ jsx(Create, { gameId, ...props }) });
};
const Create = ({ gameId, onComplete, onError, ...boxProps }) => {
const { data: result, isFetching } = useAndroidServiceAccountTestResult({ projectId: gameId });
const { data: builds } = useBuilds({ pageNumber: 0, projectId: gameId });
const previousIsFound = useRef(false);
useEffect(() => {
const isFound = getIsAppFound(result);
if (previousIsFound.current === false && isFound) {
onComplete();
}
previousIsFound.current = isFound;
}, [result]);
useSafeInput(async (input) => {
if (!gameId) return;
switch (input) {
case "r": {
queryClient.invalidateQueries({
queryKey: cacheKeys.androidKeyTestResult({ projectId: gameId })
});
break;
}
case "d": {
const dashUrl = await getShortAuthRequiredUrl(`/games/${getShortUUID(gameId)}/builds`);
await open(dashUrl);
break;
}
}
});
const initialBuild = builds?.data.find(
(build) => build.platform === Platform.ANDROID && build.buildType === BuildType.AAB
);
const downloadCmd = initialBuild ? `${getBuildSummary(initialBuild).cmd}` : "Initial AAB build not found!";
const templateVars = {
dashboardURL: new URL(`/games/${getShortUUID(gameId)}/builds`, WEB_URL).toString(),
downloadCmd
};
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, ...boxProps, children: [
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
/* @__PURE__ */ jsx(Text, { bold: true, children: isFetching ? "Checking..." : "ShipThis has not detected your game in Google Play. Press R to test again." }),
isFetching && /* @__PURE__ */ jsx(Spinner, { type: "dots" })
] }),
/* @__PURE__ */ jsx(Markdown, { filename: "create-google-play-game.md.ejs", templateVars })
] }) });
};
export { CommandContext as C, GameContext as G, Markdown as M, getJobSummary as a, CommandProvider as b, GameProvider as c, getBuildSummary as d, useSafeInput as e, useJob as f, getRenderedMarkdown as g, CreateGooglePlayGame as h, queryBuilds as q, useBuilds as u };