shipthis
Version:
ShipThis manages building and uploading your Godot games to the App Store and Google Play.
672 lines (654 loc) • 26.1 kB
JavaScript
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
import { Args } from '@oclif/core';
import { useScreenSize, withFullScreen } from 'fullscreen-ink';
import { k as cacheKeys, C as CommandContext, G as GameContext, u as useBuilds, M as Markdown, g as getShortUUID, w as useSafeInput, z as queryBuilds, n as fetchKeyTestResult, K as KeyTestStatus, p as KeyTestError, j as GameProvider, A as CreateGooglePlayGame, B as BaseAuthenticatedCommand } from '../../baseGameCommand-8VL7xe-O.js';
import fs__default from 'node:fs';
import 'node:path';
import 'chalk';
import axios from 'axios';
import 'crypto-js';
import 'uuid';
import { H as queryClient, p as getAuthedHeaders, o as API_URL, F as castArrayObjectDates, k as getProject, a8 as updateProject, t as getGodotVersion, s as GameEngine, u as createProject, v as DEFAULT_SHIPPED_FILES_GLOBS, w as DEFAULT_IGNORED_FILES_GLOBS, P as Platform, J as JobStatus, W as WEB_URL, C as CredentialsType, M as getGoogleStatus, j as isCWDGodotGame } from '../../baseCommand-CTn3KGH3.js';
import 'luxon';
import 'node:crypto';
import 'node:readline';
import 'node:url';
import 'readline-sync';
import 'isomorphic-git';
import { useMutation, useQuery } from '@tanstack/react-query';
import React, { useState, useContext, useEffect, useRef } from 'react';
import 'fast-glob';
import 'yazl';
import 'socket.io-client';
import { Box, Text } from 'ink';
import Spinner from 'ink-spinner';
import 'string-length';
import 'strip-ansi';
import 'open';
import { C as ConnectGoogle } from '../../index-CuyVBHWc.js';
import { TextInput, Alert } from '@inkjs/ui';
import 'marked';
import 'marked-terminal';
import 'qrcode';
import { J as JobLogTail } from '../../JobLogTail-Da8GuReK.js';
import { u as useShip, J as JobProgress } from '../../JobProgress-DltCQpzA.js';
import { C as CreateServiceAccountKey } from '../../index-cRnjcGxV.js';
import { C as CreateKeystore } from '../../Create-pfGYcKu4.js';
import { I as ImportKeystore } from '../../Import-D046HBaF.js';
import { a as getProjectCredentials } from '../../index-BW7z-5sB.js';
import { T as Title } from '../../Title-BCQtayg6.js';
import { C as Command } from '../../Command-Cj6F5B5a.js';
import 'fs';
import 'path';
import '@expo/apple-utils/build/index.js';
import 'deepmerge';
import 'ini';
import '../../useGoogleStatus-WqPgHteE.js';
import '../../useWebSocket-MXDbQHcu.js';
import '../../git-BpsfNFZ_.js';
import '../../ProgressSpinner-Um6ARKlk.js';
import '../../useProjectCredentials-TvlolkId.js';
import '../../RunWithSpinner-DucRnFp6.js';
import '../../import-Dk2ywOVU.js';
const useInviteServiceAccount = () => useMutation({
async mutationFn({ developerId, projectId }) {
try {
const headers = getAuthedHeaders();
const { data } = await axios.post(
`${API_URL}/projects/${projectId}/credentials/android/key/invite/`,
{ developerId },
{
headers
}
);
return data;
} catch (error) {
console.error("useInviteMutation Error", error);
throw error;
}
},
async onSuccess(data) {
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
await sleep(1e3);
queryClient.invalidateQueries({
queryKey: cacheKeys.androidKeyTestResult({ projectId: data.projectId })
});
}
});
async function queryJobs({ projectId, ...pageAndSortParams }) {
try {
const headers = getAuthedHeaders();
const url = `${API_URL}/projects/${projectId}/jobs`;
const response = await axios.get(url, { headers, params: pageAndSortParams });
return {
...response.data,
data: castArrayObjectDates(response.data.data)
};
} catch (error) {
console.warn("queryJobs Error", error);
throw error;
}
}
const useJobs = (props) => {
const queryResult = useQuery({
queryFn: async () => queryJobs(props),
queryKey: cacheKeys.jobs(props)
});
return queryResult;
};
const WIDE_BREAKPOINT = 100;
const TALL_BREAKPOINT = 35;
function useResponsive() {
const { height, width } = useScreenSize();
const isWide = width >= WIDE_BREAKPOINT;
const isTall = height >= TALL_BREAKPOINT;
return {
height,
isTall,
isWide,
width
};
}
const FormTextInput = ({ label, labelProps, ...rest }) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
/* @__PURE__ */ jsx(Text, { ...labelProps, children: label }),
/* @__PURE__ */ jsx(TextInput, { ...rest })
] });
const GameInfoForm = ({ gameInfo, onSubmit }) => {
const [activeInput, setActiveInput] = useState("name");
const [error, setError] = useState(null);
const [name, setName] = useState(gameInfo.name);
const [androidPackageName, setAndroidPackageName] = useState(gameInfo?.details?.androidPackageName);
const handleSubmitName = () => {
setError(null);
if (name.length === 0) {
setError("Please enter a name for your game");
return;
}
setActiveInput("androidPackageName");
};
const handleSubmitPackageName = () => {
setError(null);
const packageRegex = /^[A-Za-z]\w*(\.[A-Za-z]\w*)+$/;
if (!packageRegex.test(`${androidPackageName}`)) {
setError("Please enter a valid package name e.g. com.flappy.souls");
return;
}
onSubmit({
...gameInfo,
details: {
...gameInfo.details,
androidPackageName
},
name
});
};
return /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(Text, { bold: true, children: "Please confirm the following information about your game" }),
error && /* @__PURE__ */ jsx(Alert, { variant: "error", children: error }),
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [
/* @__PURE__ */ jsx(
FormTextInput,
{
defaultValue: name,
isDisabled: activeInput !== "name",
label: "Game name:",
onChange: setName,
onSubmit: handleSubmitName,
placeholder: "Enter the name of your game..."
}
),
/* @__PURE__ */ jsx(
FormTextInput,
{
defaultValue: androidPackageName,
isDisabled: activeInput !== "androidPackageName",
label: "Android package name :",
onChange: setAndroidPackageName,
onSubmit: handleSubmitPackageName,
placeholder: "e.g. com.flappy.souls"
}
)
] })
] });
};
const getGameInfo = (flagValues, project) => {
const androidPackageName = flagValues.androidPackageName || project?.details?.androidPackageName || "";
const gameInfo = {
details: {
...project?.details,
androidPackageName
},
name: project?.name || flagValues.name || ""
};
return gameInfo;
};
const CreateGame = (props) => {
const [isLoading, setIsLoading] = useState(true);
const [gameInfo, setGameInfo] = useState(null);
const [showForm, setShowForm] = useState(false);
const { command } = useContext(CommandContext);
const { game, setGameId } = useContext(GameContext);
const handleLoad = async () => {
if (!command) throw new Error("No command");
const flags = command.getDetailsFlagsValues();
const config = command.getProjectConfigSafe();
setShowForm(true);
setIsLoading(false);
const project = config.project?.id ? await getProject(config.project?.id) : void 0;
const info = getGameInfo(flags, project);
setGameInfo(info);
};
useEffect(() => {
handleLoad().catch(props.onError);
}, []);
const handleSubmitForm = async (gameInfo2) => {
try {
setShowForm(false);
setIsLoading(true);
if (!command) throw new Error("No command");
const projectConfig = command.getProjectConfigSafe();
const existingGame = projectConfig.project;
const isNew = !existingGame;
if (!isNew) {
const project2 = await updateProject(existingGame.id, gameInfo2);
const updatedConfig = {
...projectConfig,
project: project2
};
await command.setProjectConfig(updatedConfig);
return props.onComplete();
}
const { details, name } = gameInfo2;
const projectDetails = {
...details,
gameEngine: GameEngine.GODOT,
gameEngineVersion: getGodotVersion()
};
const project = await createProject({ details: projectDetails, name });
await command.setProjectConfig({
ignoredFilesGlobs: DEFAULT_IGNORED_FILES_GLOBS,
project,
shippedFilesGlobs: DEFAULT_SHIPPED_FILES_GLOBS
});
setGameId(project.id);
props.onComplete();
} catch (error) {
props.onError(error);
}
};
return /* @__PURE__ */ jsxs(Box, { borderStyle: "single", flexDirection: "column", gap: 1, margin: 1, children: [
isLoading && /* @__PURE__ */ jsx(Spinner, {}),
showForm && gameInfo && /* @__PURE__ */ jsx(GameInfoForm, { gameInfo, onSubmit: handleSubmitForm })
] });
};
const CreateInitialBuild = (props) => {
const { gameId } = useContext(GameContext);
return /* @__PURE__ */ jsx(Fragment, { children: gameId && /* @__PURE__ */ jsx(CreateForGame, { gameId, ...props }) });
};
const CreateForGame = ({ gameId, onComplete, onError, ...boxProps }) => {
const { command } = useContext(CommandContext);
const { data: buildData, isLoading: isLoadingBuilds } = useBuilds({ pageNumber: 0, projectId: gameId });
const { data: jobData, isLoading: isLoadingJobs } = useJobs({
pageNumber: 0,
projectId: gameId
});
const prevHasBuild = useRef(false);
const shipMutation = useShip();
const [shipLog, setShipLog] = useState("");
const [failedJob, setFailedJob] = useState(null);
useEffect(() => {
if (isLoadingBuilds || isLoadingJobs) return;
if (!buildData) return;
if (!jobData) return;
if (!command) return;
const hasAndroidBuild = buildData.data.some((build) => build.platform === Platform.ANDROID);
if (!prevHasBuild.current && hasAndroidBuild) return onComplete();
prevHasBuild.current = hasAndroidBuild;
const hasRunningAndroidJob = jobData.data.some(
(job) => job.type === Platform.ANDROID && [JobStatus.PENDING, JobStatus.PROCESSING].includes(job.status)
);
const shouldRun = !hasAndroidBuild && !hasRunningAndroidJob;
if (shouldRun)
shipMutation.mutateAsync({
command,
log: setShipLog,
shipFlags: {
platform: "android",
skipPublish: true
}
}).catch(onError);
}, [buildData, jobData, command]);
const androidJob = jobData?.data.find(
(job) => job.type === Platform.ANDROID && [JobStatus.PENDING, JobStatus.PROCESSING].includes(job.status)
);
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, { children: "Create an initial build..." }),
(isLoadingBuilds || isLoadingJobs || shipMutation.isPending) && /* @__PURE__ */ jsx(Spinner, { type: "dots" })
] }),
androidJob === null && /* @__PURE__ */ jsx(Text, { children: shipLog }),
androidJob && /* @__PURE__ */ jsx(JobProgress, { job: androidJob, onComplete, onFailure: (j) => {
setFailedJob(j);
setTimeout(() => {
onError(new Error(`Job ${j.id} failed`));
}, 1e3);
} }),
failedJob && /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(
Markdown,
{
filename: "ship-failure.md.ejs",
templateVars: {
jobDashboardUrl: `${WEB_URL}games/${getShortUUID(gameId)}/job/${getShortUUID(failedJob.id)}`
}
}
),
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(JobLogTail, { isWatching: false, jobId: failedJob.id, length: 10, projectId: gameId }) })
] })
] }) });
};
const InviteForm = ({ onSubmit }) => {
const [error, setError] = useState(null);
const [accountId, setAccountId] = useState("");
const handleSubmitAccountId = () => {
setError(null);
const idRegEx = /^\d{10,20}$/;
if (!idRegEx.test(`${accountId}`)) {
setError("Please enter a valid Google Play Account ID (10-20 digits)");
return;
}
return onSubmit(accountId);
};
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [
/* @__PURE__ */ jsx(
FormTextInput,
{
defaultValue: accountId,
label: "Please enter your Google Play Account ID:",
labelProps: { bold: true },
onChange: setAccountId,
onSubmit: handleSubmitAccountId,
placeholder: "e.g. 8110853839480950872"
}
),
error && /* @__PURE__ */ jsx(Alert, { variant: "error", children: error })
] }) });
};
const InviteServiceAccount = ({ onComplete, onError, ...boxProps }) => {
const { gameId } = useContext(GameContext);
const inviteMutation = useInviteServiceAccount();
const handleSubmit = async (developerId) => {
try {
if (!gameId) return;
await inviteMutation.mutateAsync({ developerId, projectId: gameId });
onComplete();
} catch (error) {
onError(error);
}
};
const templateVars = {
guideURL: new URL("/docs/guides/google-play-account-id", WEB_URL).toString()
};
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, ...boxProps, children: [
/* @__PURE__ */ jsx(Markdown, { filename: "invite-service-account.md.ejs", templateVars }),
/* @__PURE__ */ jsxs(Box, { children: [
inviteMutation.isPending && /* @__PURE__ */ jsx(Spinner, { type: "dots" }),
!inviteMutation.isPending && /* @__PURE__ */ jsx(InviteForm, { onSubmit: handleSubmit })
] })
] }) });
};
const ImportForm = ({ importKeystoreProps, onSubmit }) => {
const [activeInput, setActiveInput] = useState("jksFilePath" /* jksFilePath */);
const [error, setError] = useState(null);
const [jksFilePath, setJksFilePath] = useState(importKeystoreProps.jksFilePath);
const [password, setPassword] = useState(importKeystoreProps.keyPassword);
const handleSubmitJksFilePath = () => {
setError(null);
if (!jksFilePath || jksFilePath.length === 0) {
setError("Please enter a path to your jks file");
return;
}
if (!fs__default.existsSync(jksFilePath)) {
setError("The file does not exist");
return;
}
setActiveInput("password" /* password */);
};
const handleSubmitPassword = () => {
setError(null);
if (!password || password.length === 0) {
setError("Please enter a password");
return;
}
onSubmit({
...importKeystoreProps,
jksFilePath,
keyPassword: password,
keystorePassword: password
});
};
return /* @__PURE__ */ jsxs(Fragment, { children: [
error && /* @__PURE__ */ jsx(Alert, { variant: "error", children: error }),
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [
/* @__PURE__ */ jsx(
FormTextInput,
{
defaultValue: jksFilePath,
isDisabled: activeInput !== "jksFilePath" /* jksFilePath */,
label: "Path to your jks file:",
onChange: setJksFilePath,
onSubmit: handleSubmitJksFilePath,
placeholder: "Enter the path to your jks file..."
}
),
/* @__PURE__ */ jsx(
FormTextInput,
{
defaultValue: password,
isDisabled: activeInput !== "password" /* password */,
label: "Password:",
onChange: setPassword,
onSubmit: handleSubmitPassword,
placeholder: "Enter the password for your jks file..."
}
)
] })
] });
};
const CreateOrImport = ({ onComplete, onError, ...boxProps }) => {
const [stage, setStage] = useState(0 /* Choose */);
const [importKeystoreProps, setImportKeystoreProps] = useState({
jksFilePath: "",
keyPassword: "",
keystorePassword: ""
});
useSafeInput(async (input) => {
if (stage !== 0 /* Choose */) return;
if (input === "c") return setStage(1 /* Create */);
if (input === "i") return setStage(2 /* ImportForm */);
});
const handleImportFormSubmit = (newImportProps) => {
setImportKeystoreProps(newImportProps);
setStage(3 /* ImportKeystore */);
};
const renderStage = () => {
switch (stage) {
case 0 /* Choose */: {
return /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(Text, { children: "Would you like to create a new keystore or import an existing one?" }),
/* @__PURE__ */ jsx(Text, { bold: true, children: "Press C to create a new keystore" }),
/* @__PURE__ */ jsx(Text, { bold: true, children: "Press I to import an existing keystore" })
] });
}
case 1 /* Create */: {
return /* @__PURE__ */ jsx(CreateKeystore, { onComplete, onError });
}
case 2 /* ImportForm */: {
return /* @__PURE__ */ jsx(ImportForm, { importKeystoreProps, onSubmit: handleImportFormSubmit });
}
case 3 /* ImportKeystore */: {
return /* @__PURE__ */ jsx(ImportKeystore, { importKeystoreProps, onComplete, onError });
}
}
};
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, ...boxProps, children: [
/* @__PURE__ */ jsx(Markdown, { filename: "create-or-import-keystore.md.ejs", templateVars: {} }),
renderStage()
] });
};
var StepStatus = /* @__PURE__ */ ((StepStatus2) => {
StepStatus2["FAILURE"] = "FAILURE";
StepStatus2["PENDING"] = "PENDING";
StepStatus2["RUNNING"] = "RUNNING";
StepStatus2["SUCCESS"] = "SUCCESS";
StepStatus2["WARN"] = "WARN";
return StepStatus2;
})(StepStatus || {});
const Steps = [
"createGame",
"createKeystore",
"connectGoogle",
"createServiceAccount",
"createInitialBuild",
"createGooglePlayGame",
"inviteServiceAccount"
];
const getStepInitialStatus = (step, statusFlags) => {
if (step === "connectGoogle") {
if (!statusFlags.hasGoogleConnection && statusFlags.hasServiceAccountKey && statusFlags.hasInvitedServiceAccount)
return "WARN" /* WARN */;
return statusFlags.hasGoogleConnection ? "SUCCESS" /* SUCCESS */ : "PENDING" /* PENDING */;
}
if (step === "createInitialBuild") {
if (!statusFlags.hasInitialBuild && statusFlags.hasGooglePlayGame) return "WARN" /* WARN */;
return statusFlags.hasInitialBuild ? "SUCCESS" /* SUCCESS */ : "PENDING" /* PENDING */;
}
const base = {
createGame: statusFlags.hasGameName && statusFlags.hasAndroidPackageName,
createGooglePlayGame: statusFlags.hasGooglePlayGame,
createKeystore: statusFlags.hasAndroidKeystore,
createServiceAccount: statusFlags.hasServiceAccountKey,
inviteServiceAccount: statusFlags.hasInvitedServiceAccount
};
return base[step] ? "SUCCESS" /* SUCCESS */ : "PENDING" /* PENDING */;
};
const getStatusFlags = async (cmd) => {
const projectConfig = cmd.getProjectConfigSafe();
const projectId = projectConfig.project?.id;
const project = Boolean(projectId) && await getProject(`${projectId}`);
const hasShipThisProject = Boolean(project);
const hasGameName = project && Boolean(project?.name);
const hasAndroidPackageName = project && Boolean(project?.details?.androidPackageName);
const projectCredentials = hasShipThisProject ? await getProjectCredentials(`${projectId}`) : [];
const hasAndroidKeystore = projectCredentials.some(
(cred) => cred.isActive && cred.platform === Platform.ANDROID && cred.type === CredentialsType.CERTIFICATE
);
const googleStatus = await getGoogleStatus();
const hasGoogleConnection = googleStatus.isAuthenticated;
const hasServiceAccountKey = projectCredentials.some(
(cred) => cred.isActive && cred.platform === Platform.ANDROID && cred.type === CredentialsType.KEY
);
const buildsResponse = projectId && hasShipThisProject ? await queryBuilds({ pageNumber: 0, projectId: `${projectId}` }) : null;
const hasInitialBuild = Boolean(buildsResponse?.data?.some((build) => build.platform === Platform.ANDROID));
const testResult = projectId ? await fetchKeyTestResult({ projectId }) : null;
const hasGooglePlayGame = testResult && testResult?.status === KeyTestStatus.SUCCESS || testResult?.status === KeyTestStatus.ERROR && testResult?.error === KeyTestError.NOT_INVITED;
const hasInvitedServiceAccount = testResult ? testResult?.status === KeyTestStatus.SUCCESS : false;
return {
hasAndroidKeystore,
hasAndroidPackageName,
hasGameName,
hasGoogleConnection,
hasGooglePlayGame,
hasInitialBuild,
hasInvitedServiceAccount,
hasServiceAccountKey,
hasShipThisProject
};
};
const StepLabels = {
connectGoogle: "Connect ShipThis with Google",
createGame: "Create game in ShipThis",
createGooglePlayGame: "Create the game in Google Play",
createInitialBuild: "Create an initial build",
createKeystore: "Create or import an Android Keystore",
createServiceAccount: "Create a Service Account & API Key",
inviteServiceAccount: "Invite the Service Account"
};
const StepWithStatus = ({ position, status, title }) => {
const indicator = {
[StepStatus.FAILURE]: "\u274C",
// this is 2 wide?
[StepStatus.PENDING]: " ",
// double space
[StepStatus.RUNNING]: /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(Spinner, { type: "dots" }),
" "
] }),
[StepStatus.SUCCESS]: "\u2705",
// this is 2 wide?
[StepStatus.WARN]: "\u26A0\uFE0F "
// double
}[status];
const isBold = status !== StepStatus.PENDING;
return /* @__PURE__ */ jsxs(Text, { bold: isBold, children: [
/* @__PURE__ */ jsx(Fragment, { children: indicator }),
" ",
position,
". ",
title
] });
};
const StepStatusTable = ({ stepStatuses }) => /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginLeft: 1, children: Steps.map((step, index) => /* @__PURE__ */ jsx(StepWithStatus, { position: index + 1, status: stepStatuses[index], title: StepLabels[step] }, step)) });
const WizardHeader = ({ currentStepIndex, stepStatuses }) => {
const { isTall } = useResponsive();
const stepCount = stepStatuses ? stepStatuses.length : 0;
const currentStep = currentStepIndex === null ? null : currentStepIndex + 1;
const title = isTall ? "ShipThis Android Wizard" : `ShipThis Android Wizard (step ${currentStep} of ${stepCount})`;
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
/* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Title, { children: title }) }),
stepStatuses && isTall && /* @__PURE__ */ jsx(StepStatusTable, { stepStatuses })
] });
};
const stepComponentMap = {
connectGoogle: ConnectGoogle,
createGame: CreateGame,
createGooglePlayGame: CreateGooglePlayGame,
createInitialBuild: CreateInitialBuild,
createKeystore: CreateOrImport,
createServiceAccount: CreateServiceAccountKey,
inviteServiceAccount: InviteServiceAccount
};
const ON_COMPLETE_DELAY_MS = 500;
const AndroidWizard = (props) => {
const { command } = React.useContext(CommandContext);
const { isTall, isWide } = useResponsive();
const [currentStep, setCurrentStep] = useState(null);
const [currentStepIndex, setCurrentStepIndex] = useState(null);
const [stepStatuses, setStepStatuses] = useState(null);
const [showSuccess, setShowSuccess] = useState(false);
const determineStep = async () => {
if (!command) return;
const statusFlags = await getStatusFlags(command);
const initStatuses = Steps.map((step) => getStepInitialStatus(step, statusFlags));
const firstPending = initStatuses.indexOf(StepStatus.PENDING);
const pendingStep = firstPending === -1 ? null : Steps[firstPending];
const withPending = initStatuses.map((status, index) => {
if (index === firstPending) return StepStatus.RUNNING;
return status;
});
setCurrentStepIndex(firstPending);
setCurrentStep(pendingStep);
setStepStatuses(withPending);
const isAllDone = firstPending === -1;
setShowSuccess(isAllDone);
if (isAllDone) setTimeout(props.onComplete, ON_COMPLETE_DELAY_MS);
};
useEffect(() => {
determineStep().catch(props.onError);
}, [command]);
const handleStepComplete = () => determineStep().catch(props.onError);
const StepInterface = currentStep ? stepComponentMap[currentStep] : null;
const templateVars = {
docsURL: new URL("/docs", WEB_URL).toString(),
iosSetupURL: new URL("/docs/ios", WEB_URL).toString()
};
return /* @__PURE__ */ jsxs(GameProvider, { children: [
/* @__PURE__ */ jsx(WizardHeader, { currentStepIndex, stepStatuses }),
StepInterface && /* @__PURE__ */ jsx(
StepInterface,
{
borderStyle: isTall && isWide ? "single" : void 0,
margin: isTall && isWide ? 1 : 0,
onComplete: handleStepComplete,
onError: props.onError,
padding: isTall && isWide ? 1 : 0
}
),
showSuccess && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Markdown, { filename: "android-success.md.ejs", templateVars }) })
] });
};
class GameWizard extends BaseAuthenticatedCommand {
static args = {
platform: Args.string({
description: 'The platform to run the wizard for. This can be "android" or "ios"',
options: ["android", "ios"],
required: true
})
};
static description = "Runs all the steps for the specific platform";
static examples = ["<%= config.bin %> <%= command.id %> ios", "<%= config.bin %> <%= command.id %> android"];
static flags = {};
async run() {
const { args } = this;
if (!isCWDGodotGame()) {
this.error("No Godot project detected. Please run this from a godot project directory.", { exit: 1 });
}
if (args.platform === "ios") {
return this.config.runCommand("game:ios:wizard");
}
withFullScreen(
/* @__PURE__ */ jsx(Command, { command: this, children: /* @__PURE__ */ jsx(AndroidWizard, { onComplete: () => process.exit(0), onError: (e) => this.error(e) }) })
).start();
}
}
export { GameWizard as default };