shipthis 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/assets/markdown/android-success.md +17 -0
  2. package/assets/markdown/create-google-play-game.md +20 -0
  3. package/assets/markdown/invite-service-account.md +11 -0
  4. package/assets/markdown/privacy-notification.md +10 -0
  5. package/bin/alias-built.sh +10 -0
  6. package/dist/{AppleBundleIdDetails-BF-Pm1Ia.js → AppleBundleIdDetails-C0XqjxKJ.js} +5 -6
  7. package/dist/AppleBundleIdDetails-Dxo99Sgu.js +72 -0
  8. package/dist/Command-1YAl_0zS.js +204 -0
  9. package/dist/Command-Cl-JfhTy.js +203 -0
  10. package/dist/{CommandGame-D9wl8hfX.js → CommandGame--SAa3wEL.js} +1 -1
  11. package/dist/CommandGame-CZUx_VQu.js +8 -0
  12. package/dist/CreateKeystore-BqJdpvsI.js +56 -0
  13. package/dist/CreateKeystore-Dm0KVh85.js +56 -0
  14. package/dist/{NextSteps-DKcjSHZ3.js → NextSteps-CK9zHOCt.js} +1 -1
  15. package/dist/{ProjectCredentialsTable-BVvkIPjN.js → ProjectCredentialsTable-Ch8022rs.js} +3 -2
  16. package/dist/RunWithSpinner-BVXNWGD3.js +27 -0
  17. package/dist/{StatusTable-BzsNF75L.js → StatusTable-Dm5St4g-.js} +1 -1
  18. package/dist/Title-BCQtayg6.js +6 -0
  19. package/dist/{UserCredentialsTable-DUFQqHVt.js → UserCredentialsTable-ysmM5dlV.js} +4 -4
  20. package/dist/{baseAppleCommand-BSJhK8GA.js → baseAppleCommand-B3WQtlx-.js} +1 -1
  21. package/dist/{baseGameAndroidCommand-CPAtReqy.js → baseGameAndroidCommand-B-CFhPnE.js} +2 -2
  22. package/dist/commands/apple/apiKey/create.js +12 -12
  23. package/dist/commands/apple/apiKey/export.js +11 -11
  24. package/dist/commands/apple/apiKey/import.js +11 -11
  25. package/dist/commands/apple/apiKey/status.js +12 -13
  26. package/dist/commands/apple/certificate/create.js +12 -12
  27. package/dist/commands/apple/certificate/export.js +11 -11
  28. package/dist/commands/apple/certificate/import.js +11 -11
  29. package/dist/commands/apple/certificate/status.js +11 -12
  30. package/dist/commands/apple/login.js +2 -2
  31. package/dist/commands/apple/status.js +12 -13
  32. package/dist/commands/dashboard.js +1 -1
  33. package/dist/commands/game/android/apiKey/connect.js +12 -12
  34. package/dist/commands/game/android/apiKey/create.js +14 -12
  35. package/dist/commands/game/android/apiKey/export.js +12 -12
  36. package/dist/commands/game/android/apiKey/import.js +12 -12
  37. package/dist/commands/game/android/apiKey/invite.js +4 -4
  38. package/dist/commands/game/android/apiKey/status.js +12 -12
  39. package/dist/commands/game/android/keyStore/create.js +12 -11
  40. package/dist/commands/game/android/keyStore/export.js +11 -11
  41. package/dist/commands/game/android/keyStore/import.js +11 -11
  42. package/dist/commands/game/android/keyStore/status.js +11 -11
  43. package/dist/commands/game/android/status.js +13 -14
  44. package/dist/commands/game/android/wizard.js +533 -20
  45. package/dist/commands/game/build/download.js +10 -11
  46. package/dist/commands/game/build/list.js +10 -11
  47. package/dist/commands/game/create.js +2 -2
  48. package/dist/commands/game/details.js +10 -11
  49. package/dist/commands/game/export.js +1 -1
  50. package/dist/commands/game/ios/app/addTester.js +10 -11
  51. package/dist/commands/game/ios/app/create.js +9 -10
  52. package/dist/commands/game/ios/app/status.js +13 -14
  53. package/dist/commands/game/ios/app/sync.js +10 -11
  54. package/dist/commands/game/ios/profile/create.js +12 -12
  55. package/dist/commands/game/ios/profile/export.js +11 -11
  56. package/dist/commands/game/ios/profile/import.js +11 -11
  57. package/dist/commands/game/ios/profile/status.js +12 -12
  58. package/dist/commands/game/ios/status.js +16 -17
  59. package/dist/commands/game/ios/wizard.js +2 -2
  60. package/dist/commands/game/job/list.js +8 -10
  61. package/dist/commands/game/job/status.js +14 -13
  62. package/dist/commands/game/list.js +8 -10
  63. package/dist/commands/game/ship.js +2 -2
  64. package/dist/commands/game/status.js +13 -14
  65. package/dist/commands/game/wizard.js +1 -1
  66. package/dist/commands/internal/fastlane.js +1 -1
  67. package/dist/commands/internal/readme.js +1 -1
  68. package/dist/commands/login.js +2 -2
  69. package/dist/commands/status.js +11 -11
  70. package/dist/{export-B0FJT0EU.js → export-BiLHgSJ9.js} +1 -1
  71. package/dist/{import-CLDJ2iPu.js → import-CxNikF1c.js} +1 -1
  72. package/dist/index-B5XHQfs2.js +122 -0
  73. package/dist/{index-Df8uXQ4s.js → index-B6V7vGOj.js} +1 -1
  74. package/dist/{index-CF0fIsx2.js → index-BQRxiyqn.js} +1 -1
  75. package/dist/{index-CFHmtzfq.js → index-BuZmCvZh.js} +1 -1
  76. package/dist/index-CgBgZUkL.js +144 -0
  77. package/dist/index-DE2Hvx2o.js +122 -0
  78. package/dist/index-X__XH_Fd.js +144 -0
  79. package/dist/{upload-C5L82Yq0.js → upload-CMo3hUhl.js} +1 -1
  80. package/dist/{useAndroidServiceAccountTestResult-BnxNuoG3.js → useAndroidServiceAccountTestResult-D_30xIJA.js} +1 -1
  81. package/dist/{useAppleApp-IXRdsK5w.js → useAppleApp-DnSjUfSs.js} +1 -1
  82. package/dist/{useAppleBundleId-DYC5ISKT.js → useAppleBundleId-BNI8swhC.js} +1 -1
  83. package/dist/useJobWatching-BcBJ5dy1.js +43 -0
  84. package/dist/useJobWatching-Bz1e6xOv.js +43 -0
  85. package/dist/useProjectCredentials-B5ZmpIxL.js +54 -0
  86. package/dist/useWebSocket-DoImIdTy.js +36 -0
  87. package/npm-shrinkwrap.json +2 -2
  88. package/oclif.manifest.json +105 -105
  89. package/package.json +3 -2
  90. package/dist/Command-BrfJSeOC.js +0 -1077
@@ -1,40 +1,553 @@
1
- import { jsx } from 'react/jsx-runtime';
2
- import { render } from 'ink';
3
- import { B as BaseAuthenticatedCommand, D as DetailsFlags } from '../../../index-CF0fIsx2.js';
4
- import { C as Command, A as AndroidWizard } from '../../../Command-BrfJSeOC.js';
5
- import 'react';
6
- import 'ink-spinner';
7
- import 'axios';
1
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
2
+ import { Text, Box, render } from 'ink';
3
+ import { p as getAuthedHeaders, q as API_URL, J as castArrayObjectDates, a7 as queryClient, x as DEFAULT_SHIPPED_FILES_GLOBS, w as DEFAULT_IGNORED_FILES_GLOBS, z as getNewUploadTicket, E as startJobsFromUpload, u as getGodotVersion, t as GameEngine, v as createProject, K as JobStatus, P as Platform, Z as WEB_URL, H as getProject, C as CredentialsType, S as getGoogleStatus, B as BaseAuthenticatedCommand, D as DetailsFlags } from '../../../index-BQRxiyqn.js';
4
+ import React, { useState, useContext, useEffect, useRef } from 'react';
5
+ import { d as CommandContext, a as GameContext, b as useBuilds, M as Markdown, q as queryBuilds, G as GameProvider, e as CreateGooglePlayGame, C as Command } from '../../../Command-1YAl_0zS.js';
6
+ import Spinner from 'ink-spinner';
7
+ import { Alert, TextInput } from '@inkjs/ui';
8
+ import axios from 'axios';
8
9
  import 'crypto-js';
9
- import 'uuid';
10
+ import { v4 } from 'uuid';
10
11
  import 'luxon';
11
- import 'fs';
12
- import '@inkjs/ui';
12
+ import fs__default from 'fs';
13
+ import { useQuery, useMutation } from '@tanstack/react-query';
14
+ import 'open';
13
15
  import 'crypto';
14
16
  import 'readline-sync';
15
17
  import 'node:readline';
16
18
  import 'node:path';
17
19
  import 'node:url';
18
- import '@tanstack/react-query';
19
- import 'fast-glob';
20
- import 'yazl';
20
+ import fg from 'fast-glob';
21
+ import yazl from 'yazl';
21
22
  import 'socket.io-client';
22
23
  import 'isomorphic-git';
23
24
  import '@oclif/core';
24
- import 'marked';
25
- import 'marked-terminal';
26
25
  import 'qrcode';
27
26
  import 'string-length';
28
27
  import 'strip-ansi';
28
+ import { C as CreateKeystore } from '../../../CreateKeystore-BqJdpvsI.js';
29
+ import { C as ConnectGoogle } from '../../../index-DE2Hvx2o.js';
30
+ import { P as ProgressSpinner, C as CreateServiceAccountKey } from '../../../index-X__XH_Fd.js';
31
+ import { c as cacheKeys, f as fetchKeyTestResult, K as KeyTestStatus, a as KeyTestError } from '../../../useAndroidServiceAccountTestResult-D_30xIJA.js';
32
+ import { c as getFileHash } from '../../../index-B6V7vGOj.js';
33
+ import { g as getCWDGitInfo } from '../../../git-DREGq-jc.js';
34
+ import { u as useJobWatching } from '../../../useJobWatching-BcBJ5dy1.js';
35
+ import { a as getProjectCredentials } from '../../../index-BuZmCvZh.js';
36
+ import { T as Title } from '../../../Title-BCQtayg6.js';
29
37
  import 'path';
30
38
  import '@expo/apple-utils/build/index.js';
31
39
  import 'ini';
32
40
  import 'deepmerge';
33
- import 'open';
34
- import '../../../index-Df8uXQ4s.js';
35
- import '../../../useAndroidServiceAccountTestResult-BnxNuoG3.js';
36
- import '../../../git-DREGq-jc.js';
37
- import '../../../index-CFHmtzfq.js';
41
+ import 'marked';
42
+ import 'marked-terminal';
43
+ import '../../../RunWithSpinner-BVXNWGD3.js';
44
+ import '../../../useWebSocket-DoImIdTy.js';
45
+ import '../../../useProjectCredentials-B5ZmpIxL.js';
46
+
47
+ async function queryJobs({ projectId, ...pageAndSortParams }) {
48
+ try {
49
+ const headers = getAuthedHeaders();
50
+ const url = `${API_URL}/projects/${projectId}/jobs`;
51
+ const response = await axios.get(url, { headers, params: pageAndSortParams });
52
+ return {
53
+ ...response.data,
54
+ data: castArrayObjectDates(response.data.data)
55
+ };
56
+ } catch (error) {
57
+ console.warn("queryJobs Error", error);
58
+ throw error;
59
+ }
60
+ }
61
+ const useJobs = (props) => {
62
+ const queryResult = useQuery({
63
+ queryKey: cacheKeys.jobs(props),
64
+ queryFn: async () => queryJobs(props)
65
+ });
66
+ return queryResult;
67
+ };
68
+
69
+ async function ship(command) {
70
+ const projectConfig = await command.getProjectConfig();
71
+ if (!projectConfig.project) throw new Error("No project found in project config");
72
+ const shippedFilesGlobs = projectConfig.shippedFilesGlobs || DEFAULT_SHIPPED_FILES_GLOBS;
73
+ const ignoredFilesGlobs = projectConfig.ignoredFilesGlobs || DEFAULT_IGNORED_FILES_GLOBS;
74
+ const files = await fg(shippedFilesGlobs, { dot: true, ignore: ignoredFilesGlobs });
75
+ const zipFile = new yazl.ZipFile();
76
+ for (const file of files) {
77
+ zipFile.addFile(file, file);
78
+ }
79
+ const outputZipToFile = (zip, fileName) => new Promise((resolve) => {
80
+ const outputStream = fs__default.createWriteStream(fileName);
81
+ zip.outputStream.pipe(outputStream).on("close", () => resolve());
82
+ zip.end();
83
+ });
84
+ const tmpZipFile = `${process.cwd()}/shipthis-${v4()}.zip`;
85
+ await outputZipToFile(zipFile, tmpZipFile);
86
+ const zipBuffer = fs__default.readFileSync(tmpZipFile);
87
+ const { size } = fs__default.statSync(tmpZipFile);
88
+ const uploadTicket = await getNewUploadTicket(projectConfig.project.id);
89
+ await axios.put(uploadTicket.url, zipBuffer, {
90
+ headers: {
91
+ "Content-length": size,
92
+ "Content-Type": "application/zip"
93
+ }
94
+ });
95
+ const gitInfo = await getCWDGitInfo();
96
+ const zipFileMd5 = await getFileHash(tmpZipFile);
97
+ const uploadDetails = {
98
+ ...gitInfo,
99
+ zipFileMd5
100
+ };
101
+ const jobs = await startJobsFromUpload(uploadTicket.id, uploadDetails);
102
+ return jobs;
103
+ }
104
+ const useShip = () => {
105
+ return useMutation({
106
+ mutationFn: ship,
107
+ onSuccess: async (data) => {
108
+ queryClient.invalidateQueries({
109
+ queryKey: cacheKeys.jobs({ projectId: data[0].project.id, pageNumber: 0 })
110
+ });
111
+ }
112
+ });
113
+ };
114
+
115
+ const useInviteServiceAccount = () => {
116
+ return useMutation({
117
+ mutationFn: async ({ projectId, developerId }) => {
118
+ try {
119
+ const headers = getAuthedHeaders();
120
+ const { data } = await axios.post(
121
+ `${API_URL}/projects/${projectId}/credentials/android/key/invite/`,
122
+ { developerId },
123
+ {
124
+ headers
125
+ }
126
+ );
127
+ return data;
128
+ } catch (error) {
129
+ console.error("useInviteMutation Error", error);
130
+ throw error;
131
+ }
132
+ },
133
+ onSuccess: async (data) => {
134
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
135
+ await sleep(1e3);
136
+ queryClient.invalidateQueries({
137
+ queryKey: cacheKeys.androidKeyTestResult({ projectId: data.projectId })
138
+ });
139
+ }
140
+ });
141
+ };
142
+
143
+ const GameInfoForm = ({ gameInfo, onSubmit }) => {
144
+ const [activeInput, setActiveInput] = useState("name");
145
+ const [error, setError] = useState(null);
146
+ const [name, setName] = useState(gameInfo.name);
147
+ const [androidPackageName, setAndroidPackageName] = useState(gameInfo?.details?.androidPackageName);
148
+ const handleSubmitName = () => {
149
+ setError(null);
150
+ if (name.length === 0) {
151
+ setError("Please enter a name for your game");
152
+ return;
153
+ }
154
+ setActiveInput("androidPackageName");
155
+ };
156
+ const handleSubmitPackageName = () => {
157
+ setError(null);
158
+ const packageRegex = /^[a-zA-Z][a-zA-Z0-9_]*(\.[a-zA-Z][a-zA-Z0-9_]*)+$/;
159
+ if (!packageRegex.test(`${androidPackageName}`)) {
160
+ setError("Please enter a valid package name e.g. com.flappy.souls");
161
+ return;
162
+ }
163
+ onSubmit({
164
+ ...gameInfo,
165
+ name,
166
+ details: {
167
+ ...gameInfo.details,
168
+ androidPackageName
169
+ }
170
+ });
171
+ };
172
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
173
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Please confirm the following information about your game" }),
174
+ error && /* @__PURE__ */ jsx(Alert, { variant: "error", children: error }),
175
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [
176
+ /* @__PURE__ */ jsx(
177
+ FormTextInput,
178
+ {
179
+ label: "Game name:",
180
+ isDisabled: activeInput !== "name",
181
+ defaultValue: name,
182
+ placeholder: "Enter the name of your game...",
183
+ onChange: setName,
184
+ onSubmit: handleSubmitName
185
+ }
186
+ ),
187
+ /* @__PURE__ */ jsx(
188
+ FormTextInput,
189
+ {
190
+ label: "Android package name :",
191
+ isDisabled: activeInput !== "androidPackageName",
192
+ defaultValue: androidPackageName,
193
+ placeholder: "e.g. com.flappy.souls",
194
+ onChange: setAndroidPackageName,
195
+ onSubmit: handleSubmitPackageName
196
+ }
197
+ )
198
+ ] })
199
+ ] });
200
+ };
201
+
202
+ const getGameInfo = (flagValues, project) => {
203
+ const androidPackageName = flagValues.androidPackageName || project?.details?.androidPackageName || "";
204
+ const gameInfo = {
205
+ name: project?.name || flagValues.name || "",
206
+ details: {
207
+ ...project?.details,
208
+ androidPackageName
209
+ }
210
+ };
211
+ return gameInfo;
212
+ };
213
+ const CreateGame = (props) => {
214
+ const [isLoading, setIsLoading] = useState(true);
215
+ const [gameInfo, setGameInfo] = useState(null);
216
+ const [showForm, setShowForm] = useState(false);
217
+ const { command } = useContext(CommandContext);
218
+ const { setGameId, game } = useContext(GameContext);
219
+ const handleLoad = async () => {
220
+ if (!command) return;
221
+ const flagValues = command.getDetailsFlagsValues();
222
+ const projectConfig = await command.getProjectConfigSafe();
223
+ if (!projectConfig.project) {
224
+ setShowForm(true);
225
+ setIsLoading(false);
226
+ const gameInfo3 = getGameInfo(flagValues);
227
+ setGameInfo(gameInfo3);
228
+ return;
229
+ }
230
+ const gameInfo2 = getGameInfo(flagValues, game || undefined);
231
+ setGameInfo(gameInfo2);
232
+ setShowForm(true);
233
+ setIsLoading(false);
234
+ };
235
+ useEffect(() => {
236
+ handleLoad();
237
+ }, []);
238
+ const handleGameInfoSubmit = async (gameInfo2) => {
239
+ if (!command) return;
240
+ setShowForm(false);
241
+ setIsLoading(true);
242
+ const isNew = !(await command.getProjectConfigSafe()).project;
243
+ if (!isNew) {
244
+ const cmd = command;
245
+ await cmd.updateGame(gameInfo2);
246
+ return props.onComplete();
247
+ }
248
+ const { name, details } = gameInfo2;
249
+ const projectDetails = {
250
+ ...details,
251
+ gameEngine: GameEngine.GODOT,
252
+ gameEngineVersion: getGodotVersion()
253
+ };
254
+ const project = await createProject({ name, details: projectDetails });
255
+ await command.setProjectConfig({
256
+ project,
257
+ shippedFilesGlobs: DEFAULT_SHIPPED_FILES_GLOBS,
258
+ ignoredFilesGlobs: DEFAULT_IGNORED_FILES_GLOBS
259
+ });
260
+ setGameId(project.id);
261
+ props.onComplete();
262
+ };
263
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, borderStyle: "single", margin: 1, children: [
264
+ isLoading && /* @__PURE__ */ jsx(Spinner, {}),
265
+ showForm && gameInfo && /* @__PURE__ */ jsx(GameInfoForm, { gameInfo, onSubmit: handleGameInfoSubmit })
266
+ ] });
267
+ };
268
+
269
+ const FormTextInput = ({ label, labelProps, ...rest }) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
270
+ /* @__PURE__ */ jsx(Text, { ...labelProps, children: label }),
271
+ /* @__PURE__ */ jsx(TextInput, { ...rest })
272
+ ] });
273
+
274
+ const JobProgress = (props) => {
275
+ const prevJobStatus = useRef(props.job.status);
276
+ const handleJobUpdate = (job) => {
277
+ const completed = [JobStatus.COMPLETED, JobStatus.FAILED];
278
+ const wasRunning = !completed.includes(prevJobStatus.current);
279
+ if (completed.includes(job.status) && wasRunning) {
280
+ props.onComplete();
281
+ }
282
+ prevJobStatus.current = job.status;
283
+ };
284
+ const { progress } = useJobWatching({
285
+ projectId: props.job.project.id,
286
+ jobId: props.job.id,
287
+ isWatching: true,
288
+ onJobUpdate: handleJobUpdate
289
+ });
290
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(ProgressSpinner, { progress, label: "Job progress...", spinnerType: "dots" }) });
291
+ };
292
+
293
+ const CreateInitialBuild = (props) => {
294
+ const { gameId } = useContext(GameContext);
295
+ return /* @__PURE__ */ jsx(Fragment, { children: gameId && /* @__PURE__ */ jsx(CreateForGame, { gameId, ...props }) });
296
+ };
297
+ const CreateForGame = ({ onComplete, onError, gameId, ...boxProps }) => {
298
+ const { command } = useContext(CommandContext);
299
+ const { data: buildData, isLoading: isLoadingBuilds } = useBuilds({ projectId: gameId, pageNumber: 0 });
300
+ const { data: jobData, isLoading: isLoadingJobs } = useJobs({
301
+ projectId: gameId,
302
+ pageNumber: 0
303
+ });
304
+ const prevHasBuild = useRef(false);
305
+ const shipMutation = useShip();
306
+ useEffect(() => {
307
+ if (isLoadingBuilds || isLoadingJobs) return;
308
+ if (!buildData) return;
309
+ if (!jobData) return;
310
+ if (!command) return;
311
+ const hasAndroidBuild = buildData.data.some((build) => build.platform === Platform.ANDROID);
312
+ if (!prevHasBuild.current && hasAndroidBuild) return onComplete();
313
+ prevHasBuild.current = hasAndroidBuild;
314
+ const hasAndroidJob = jobData.data.some((job) => job.type === Platform.ANDROID);
315
+ const shouldRun = !hasAndroidBuild && !hasAndroidJob;
316
+ if (shouldRun) shipMutation.mutateAsync(command).catch(onError);
317
+ }, [buildData, jobData, command]);
318
+ const androidJob = jobData?.data.find(
319
+ (job) => job.type === Platform.ANDROID && [JobStatus.PENDING, JobStatus.PROCESSING].includes(job.status)
320
+ );
321
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, ...boxProps, children: [
322
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
323
+ /* @__PURE__ */ jsx(Text, { children: "Create an initial build..." }),
324
+ (isLoadingBuilds || isLoadingJobs || shipMutation.isPending) && /* @__PURE__ */ jsx(Spinner, { type: "dots" })
325
+ ] }),
326
+ androidJob && /* @__PURE__ */ jsx(JobProgress, { job: androidJob, onComplete })
327
+ ] }) });
328
+ };
329
+
330
+ const InviteForm = ({ onSubmit }) => {
331
+ const [error, setError] = useState(null);
332
+ const [accountId, setAccountId] = useState("");
333
+ const handleSubmitAccountId = () => {
334
+ setError(null);
335
+ const idRegEx = /^\d{10,20}$/;
336
+ if (!idRegEx.test(`${accountId}`)) {
337
+ setError("Please enter a valid Google Play Account ID (10-20 digits)");
338
+ return;
339
+ }
340
+ return onSubmit(accountId);
341
+ };
342
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [
343
+ /* @__PURE__ */ jsx(
344
+ FormTextInput,
345
+ {
346
+ label: "Please enter your Google Play Account ID:",
347
+ labelProps: { bold: true },
348
+ defaultValue: accountId,
349
+ placeholder: "e.g. 8110853839480950872",
350
+ onChange: setAccountId,
351
+ onSubmit: handleSubmitAccountId
352
+ }
353
+ ),
354
+ error && /* @__PURE__ */ jsx(Alert, { variant: "error", children: error })
355
+ ] }) });
356
+ };
357
+
358
+ const InviteServiceAccount = ({ onComplete, onError, ...boxProps }) => {
359
+ const { gameId } = useContext(GameContext);
360
+ const inviteMutation = useInviteServiceAccount();
361
+ const handleSubmit = async (developerId) => {
362
+ try {
363
+ if (!gameId) return;
364
+ await inviteMutation.mutateAsync({ projectId: gameId, developerId });
365
+ onComplete();
366
+ } catch (error) {
367
+ onError(error);
368
+ }
369
+ };
370
+ const templateVars = {
371
+ guideURL: new URL("/docs/guides/google-play-account-id", WEB_URL).toString()
372
+ };
373
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, ...boxProps, children: [
374
+ /* @__PURE__ */ jsx(Markdown, { filename: "invite-service-account.md", templateVars }),
375
+ /* @__PURE__ */ jsxs(Box, { children: [
376
+ inviteMutation.isPending && /* @__PURE__ */ jsx(Spinner, { type: "dots" }),
377
+ !inviteMutation.isPending && /* @__PURE__ */ jsx(InviteForm, { onSubmit: handleSubmit })
378
+ ] })
379
+ ] }) });
380
+ };
381
+
382
+ var StepStatus = /* @__PURE__ */ ((StepStatus2) => {
383
+ StepStatus2["PENDING"] = "PENDING";
384
+ StepStatus2["RUNNING"] = "RUNNING";
385
+ StepStatus2["SUCCESS"] = "SUCCESS";
386
+ StepStatus2["FAILURE"] = "FAILURE";
387
+ StepStatus2["WARN"] = "WARN";
388
+ return StepStatus2;
389
+ })(StepStatus || {});
390
+ const Steps = [
391
+ "createGame",
392
+ "createKeystore",
393
+ "connectGoogle",
394
+ "createServiceAccount",
395
+ "createInitialBuild",
396
+ "createGooglePlayGame",
397
+ "inviteServiceAccount"
398
+ ];
399
+ const getStepInitialStatus = (step, statusFlags) => {
400
+ const base = {
401
+ gameInfo: statusFlags.hasGameName && statusFlags.hasAndroidPackageName,
402
+ createGame: statusFlags.hasShipThisProject,
403
+ createKeystore: statusFlags.hasAndroidKeystore,
404
+ createServiceAccount: statusFlags.hasServiceAccountKey,
405
+ createGooglePlayGame: statusFlags.hasGooglePlayGame,
406
+ inviteServiceAccount: statusFlags.hasInvitedServiceAccount
407
+ };
408
+ if (step in base) return base[step] ? "SUCCESS" /* SUCCESS */ : "PENDING" /* PENDING */;
409
+ if (step === "connectGoogle") {
410
+ if (!statusFlags.hasGoogleConnection && statusFlags.hasServiceAccountKey && statusFlags.hasInvitedServiceAccount)
411
+ return "WARN" /* WARN */;
412
+ return statusFlags.hasGoogleConnection ? "SUCCESS" /* SUCCESS */ : "PENDING" /* PENDING */;
413
+ }
414
+ if (step === "createInitialBuild") {
415
+ if (!statusFlags.hasInitialBuild && statusFlags.hasGooglePlayGame) return "WARN" /* WARN */;
416
+ return statusFlags.hasInitialBuild ? "SUCCESS" /* SUCCESS */ : "PENDING" /* PENDING */;
417
+ }
418
+ throw new Error(`Unknown step: ${step}`);
419
+ };
420
+ const getStatusFlags = async (cmd) => {
421
+ const projectConfig = await cmd.getProjectConfigSafe();
422
+ const projectId = projectConfig.project?.id;
423
+ const project = !!projectId && await getProject(projectId);
424
+ const hasShipThisProject = !!project;
425
+ const hasGameName = project && !!project?.name;
426
+ const hasAndroidPackageName = project && !!project?.details?.androidPackageName;
427
+ const projectCredentials = hasShipThisProject ? await getProjectCredentials(project.id) : [];
428
+ const hasAndroidKeystore = projectCredentials.some(
429
+ (cred) => cred.isActive && cred.platform === Platform.ANDROID && cred.type == CredentialsType.CERTIFICATE
430
+ );
431
+ const googleStatus = await getGoogleStatus();
432
+ const hasGoogleConnection = googleStatus.isAuthenticated;
433
+ const hasServiceAccountKey = projectCredentials.some(
434
+ (cred) => cred.isActive && cred.platform == Platform.ANDROID && cred.type == CredentialsType.KEY
435
+ );
436
+ const buildsResponse = !!projectId && hasShipThisProject && await queryBuilds({ projectId, pageNumber: 0 });
437
+ const hasInitialBuild = !!buildsResponse && buildsResponse.data.length > 0;
438
+ const testResult = projectId ? await fetchKeyTestResult({ projectId }) : null;
439
+ const hasGooglePlayGame = testResult && testResult?.status === KeyTestStatus.SUCCESS || testResult?.status === KeyTestStatus.ERROR && testResult?.error === KeyTestError.NOT_INVITED;
440
+ const hasInvitedServiceAccount = testResult ? testResult?.status === KeyTestStatus.SUCCESS : false;
441
+ return {
442
+ hasShipThisProject,
443
+ hasGameName,
444
+ hasAndroidPackageName,
445
+ hasAndroidKeystore,
446
+ hasGoogleConnection,
447
+ hasServiceAccountKey,
448
+ hasInitialBuild,
449
+ hasGooglePlayGame,
450
+ hasInvitedServiceAccount
451
+ };
452
+ };
453
+
454
+ const StepLabels = {
455
+ createGame: "Create game in ShipThis",
456
+ createKeystore: "Create an Android Keystore",
457
+ connectGoogle: "Connect ShipThis with Google",
458
+ createServiceAccount: "Create a Service Account & API Key",
459
+ createInitialBuild: "Create an initial build",
460
+ createGooglePlayGame: "Create the game in Google Play",
461
+ inviteServiceAccount: "Invite the Service Account"
462
+ };
463
+ const StepWithStatus = ({ position, title, status }) => {
464
+ const indicator = {
465
+ [StepStatus.PENDING]: " ",
466
+ // double space
467
+ [StepStatus.RUNNING]: /* @__PURE__ */ jsxs(Fragment, { children: [
468
+ /* @__PURE__ */ jsx(Spinner, { type: "dots" }),
469
+ " "
470
+ ] }),
471
+ [StepStatus.SUCCESS]: "\u2705",
472
+ // this is 2 wide?
473
+ [StepStatus.FAILURE]: "\u274C",
474
+ // this is 2 wide?
475
+ [StepStatus.WARN]: "\u26A0\uFE0F "
476
+ // double
477
+ }[status];
478
+ const isBold = status !== StepStatus.PENDING;
479
+ return /* @__PURE__ */ jsxs(Text, { bold: isBold, children: [
480
+ /* @__PURE__ */ jsx(Fragment, { children: indicator }),
481
+ " ",
482
+ position,
483
+ ". ",
484
+ title
485
+ ] });
486
+ };
487
+ const StepStatusTable = ({ stepStatuses }) => {
488
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginLeft: 1, children: Steps.map((step, index) => {
489
+ return /* @__PURE__ */ jsx(StepWithStatus, { position: index + 1, title: StepLabels[step], status: stepStatuses[index] }, step);
490
+ }) });
491
+ };
492
+
493
+ const stepComponentMap = {
494
+ createGame: CreateGame,
495
+ createKeystore: CreateKeystore,
496
+ connectGoogle: ConnectGoogle,
497
+ createServiceAccount: CreateServiceAccountKey,
498
+ createInitialBuild: CreateInitialBuild,
499
+ createGooglePlayGame: CreateGooglePlayGame,
500
+ inviteServiceAccount: InviteServiceAccount
501
+ };
502
+ const ON_COMPLETE_DELAY_MS = 500;
503
+ const AndroidWizard = (props) => {
504
+ const { command } = React.useContext(CommandContext);
505
+ const [currentStep, setCurrentStep] = useState(null);
506
+ const [stepStatuses, setStepStatuses] = useState(null);
507
+ const [showSuccess, setShowSuccess] = useState(false);
508
+ const determineStep = async () => {
509
+ if (!command) return;
510
+ const statusFlags = await getStatusFlags(command);
511
+ const initStatuses = Steps.map((step) => getStepInitialStatus(step, statusFlags));
512
+ const firstPending = initStatuses.findIndex((status) => status === StepStatus.PENDING);
513
+ const pendingStep = firstPending === -1 ? null : Steps[firstPending];
514
+ const withPending = initStatuses.map((status, index) => {
515
+ if (index === firstPending) return StepStatus.RUNNING;
516
+ return status;
517
+ });
518
+ setCurrentStep(pendingStep);
519
+ setStepStatuses(withPending);
520
+ const isAllDone = firstPending === -1;
521
+ setShowSuccess(isAllDone);
522
+ if (isAllDone) setTimeout(props.onComplete, ON_COMPLETE_DELAY_MS);
523
+ };
524
+ useEffect(() => {
525
+ determineStep().catch(props.onError);
526
+ }, [command]);
527
+ const handleStepComplete = () => determineStep().catch(props.onError);
528
+ const StepInterface = currentStep ? stepComponentMap[currentStep] : null;
529
+ const templateVars = {
530
+ iosSetupURL: new URL("/docs/ios", WEB_URL).toString(),
531
+ docsURL: new URL("/docs", WEB_URL).toString()
532
+ };
533
+ return /* @__PURE__ */ jsxs(GameProvider, { children: [
534
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
535
+ /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Title, { children: "ShipThis Android Wizard" }) }),
536
+ stepStatuses && /* @__PURE__ */ jsx(StepStatusTable, { stepStatuses })
537
+ ] }),
538
+ StepInterface && /* @__PURE__ */ jsx(
539
+ StepInterface,
540
+ {
541
+ onComplete: handleStepComplete,
542
+ onError: props.onError,
543
+ margin: 1,
544
+ borderStyle: "single",
545
+ padding: 1
546
+ }
547
+ ),
548
+ showSuccess && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Markdown, { filename: "android-success.md", templateVars }) })
549
+ ] });
550
+ };
38
551
 
39
552
  class GameAndroidWizard extends BaseAuthenticatedCommand {
40
553
  static args = {};
@@ -2,27 +2,27 @@ import { jsx } from 'react/jsx-runtime';
2
2
  import { Args, Flags } from '@oclif/core';
3
3
  import { render } from 'ink';
4
4
  import * as fs from 'fs';
5
- import { c as BaseGameCommand, I as getBuild } from '../../../index-CF0fIsx2.js';
6
- import { C as Command, R as RunWithSpinner } from '../../../Command-BrfJSeOC.js';
5
+ import { c as BaseGameCommand, I as getBuild } from '../../../index-BQRxiyqn.js';
7
6
  import 'react';
7
+ import { C as Command } from '../../../Command-1YAl_0zS.js';
8
8
  import 'ink-spinner';
9
9
  import '@inkjs/ui';
10
+ import axios from 'axios';
11
+ import '@tanstack/react-query';
12
+ import 'open';
10
13
  import 'crypto';
11
14
  import 'readline-sync';
12
15
  import 'node:readline';
13
16
  import 'node:path';
14
17
  import 'node:url';
15
- import axios from 'axios';
16
- import '@tanstack/react-query';
17
18
  import 'luxon';
18
19
  import 'uuid';
19
20
  import 'fast-glob';
20
21
  import 'yazl';
21
22
  import 'socket.io-client';
22
23
  import 'isomorphic-git';
23
- import 'marked';
24
- import 'marked-terminal';
25
24
  import 'qrcode';
25
+ import { R as RunWithSpinner } from '../../../RunWithSpinner-BVXNWGD3.js';
26
26
  import 'string-length';
27
27
  import 'strip-ansi';
28
28
  import 'path';
@@ -30,11 +30,10 @@ import '@expo/apple-utils/build/index.js';
30
30
  import 'crypto-js';
31
31
  import 'ini';
32
32
  import 'deepmerge';
33
- import 'open';
34
- import '../../../index-Df8uXQ4s.js';
35
- import '../../../useAndroidServiceAccountTestResult-BnxNuoG3.js';
36
- import '../../../git-DREGq-jc.js';
37
- import '../../../index-CFHmtzfq.js';
33
+ import '../../../index-B6V7vGOj.js';
34
+ import '../../../useAndroidServiceAccountTestResult-D_30xIJA.js';
35
+ import 'marked';
36
+ import 'marked-terminal';
38
37
 
39
38
  class GameBuildDownload extends BaseGameCommand {
40
39
  static args = {
@@ -1,9 +1,9 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { Box, Text, render } from 'ink';
3
3
  import { Flags } from '@oclif/core';
4
- import { c as BaseGameCommand } from '../../../index-CF0fIsx2.js';
5
- import { e as useBuilds, T as Title, f as getBuildSummary } from '../../../Command-BrfJSeOC.js';
4
+ import { c as BaseGameCommand } from '../../../index-BQRxiyqn.js';
6
5
  import 'react';
6
+ import { b as useBuilds, c as getBuildSummary } from '../../../Command-1YAl_0zS.js';
7
7
  import Spinner from 'ink-spinner';
8
8
  import 'axios';
9
9
  import 'crypto-js';
@@ -11,32 +11,31 @@ import 'uuid';
11
11
  import 'luxon';
12
12
  import 'fs';
13
13
  import '@inkjs/ui';
14
+ import '@tanstack/react-query';
15
+ import 'open';
14
16
  import 'crypto';
15
17
  import 'readline-sync';
16
18
  import 'node:readline';
17
19
  import 'node:path';
18
20
  import 'node:url';
19
- import '@tanstack/react-query';
20
21
  import 'fast-glob';
21
22
  import 'yazl';
22
23
  import 'socket.io-client';
23
24
  import 'isomorphic-git';
24
- import 'marked';
25
- import 'marked-terminal';
26
25
  import 'qrcode';
27
26
  import 'string-length';
28
27
  import 'strip-ansi';
29
- import { C as CommandGame } from '../../../CommandGame-D9wl8hfX.js';
28
+ import { C as CommandGame } from '../../../CommandGame-CZUx_VQu.js';
30
29
  import { T as Table } from '../../../Table-CvM6pccN.js';
30
+ import { T as Title } from '../../../Title-BCQtayg6.js';
31
31
  import 'path';
32
32
  import '@expo/apple-utils/build/index.js';
33
33
  import 'ini';
34
34
  import 'deepmerge';
35
- import 'open';
36
- import '../../../index-Df8uXQ4s.js';
37
- import '../../../useAndroidServiceAccountTestResult-BnxNuoG3.js';
38
- import '../../../git-DREGq-jc.js';
39
- import '../../../index-CFHmtzfq.js';
35
+ import '../../../index-B6V7vGOj.js';
36
+ import '../../../useAndroidServiceAccountTestResult-D_30xIJA.js';
37
+ import 'marked';
38
+ import 'marked-terminal';
40
39
 
41
40
  const BuildsTable = ({ queryProps, ...boxProps }) => {
42
41
  const { isLoading, data } = useBuilds(queryProps);
@@ -1,6 +1,6 @@
1
1
  import { Flags } from '@oclif/core';
2
- import { B as BaseAuthenticatedCommand, D as DetailsFlags, j as isCWDGodotGame, t as GameEngine, u as getGodotVersion, v as createProject, w as DEFAULT_IGNORED_FILES_GLOBS, x as DEFAULT_SHIPPED_FILES_GLOBS, y as getGodotProjectName } from '../../index-CF0fIsx2.js';
3
- import { a as getInput } from '../../index-Df8uXQ4s.js';
2
+ import { B as BaseAuthenticatedCommand, D as DetailsFlags, j as isCWDGodotGame, t as GameEngine, u as getGodotVersion, v as createProject, w as DEFAULT_IGNORED_FILES_GLOBS, x as DEFAULT_SHIPPED_FILES_GLOBS, y as getGodotProjectName } from '../../index-BQRxiyqn.js';
3
+ import { a as getInput } from '../../index-B6V7vGOj.js';
4
4
  import 'path';
5
5
  import 'fs';
6
6
  import '@expo/apple-utils/build/index.js';