UNPKG

shipthis

Version:

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

672 lines (654 loc) 26.1 kB
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 };