shipthis 0.1.2 → 0.1.3

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 (83) 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/Command-Cl-JfhTy.js +203 -0
  8. package/dist/{CommandGame-D9wl8hfX.js → CommandGame--SAa3wEL.js} +1 -1
  9. package/dist/CreateKeystore-Dm0KVh85.js +56 -0
  10. package/dist/{NextSteps-DKcjSHZ3.js → NextSteps-CK9zHOCt.js} +1 -1
  11. package/dist/{ProjectCredentialsTable-BVvkIPjN.js → ProjectCredentialsTable-Ch8022rs.js} +3 -2
  12. package/dist/RunWithSpinner-BVXNWGD3.js +27 -0
  13. package/dist/{StatusTable-BzsNF75L.js → StatusTable-Dm5St4g-.js} +1 -1
  14. package/dist/Title-BCQtayg6.js +6 -0
  15. package/dist/{UserCredentialsTable-DUFQqHVt.js → UserCredentialsTable-ysmM5dlV.js} +4 -4
  16. package/dist/{baseAppleCommand-BSJhK8GA.js → baseAppleCommand-B3WQtlx-.js} +1 -1
  17. package/dist/{baseGameAndroidCommand-CPAtReqy.js → baseGameAndroidCommand-B-CFhPnE.js} +2 -2
  18. package/dist/commands/apple/apiKey/create.js +12 -12
  19. package/dist/commands/apple/apiKey/export.js +11 -11
  20. package/dist/commands/apple/apiKey/import.js +11 -11
  21. package/dist/commands/apple/apiKey/status.js +12 -13
  22. package/dist/commands/apple/certificate/create.js +12 -12
  23. package/dist/commands/apple/certificate/export.js +11 -11
  24. package/dist/commands/apple/certificate/import.js +11 -11
  25. package/dist/commands/apple/certificate/status.js +11 -12
  26. package/dist/commands/apple/login.js +2 -2
  27. package/dist/commands/apple/status.js +12 -13
  28. package/dist/commands/dashboard.js +1 -1
  29. package/dist/commands/game/android/apiKey/connect.js +12 -12
  30. package/dist/commands/game/android/apiKey/create.js +14 -12
  31. package/dist/commands/game/android/apiKey/export.js +12 -12
  32. package/dist/commands/game/android/apiKey/import.js +12 -12
  33. package/dist/commands/game/android/apiKey/invite.js +4 -4
  34. package/dist/commands/game/android/apiKey/status.js +12 -12
  35. package/dist/commands/game/android/keyStore/create.js +12 -11
  36. package/dist/commands/game/android/keyStore/export.js +11 -11
  37. package/dist/commands/game/android/keyStore/import.js +11 -11
  38. package/dist/commands/game/android/keyStore/status.js +11 -11
  39. package/dist/commands/game/android/status.js +13 -14
  40. package/dist/commands/game/android/wizard.js +533 -20
  41. package/dist/commands/game/build/download.js +10 -11
  42. package/dist/commands/game/build/list.js +10 -11
  43. package/dist/commands/game/create.js +2 -2
  44. package/dist/commands/game/details.js +10 -11
  45. package/dist/commands/game/export.js +1 -1
  46. package/dist/commands/game/ios/app/addTester.js +10 -11
  47. package/dist/commands/game/ios/app/create.js +9 -10
  48. package/dist/commands/game/ios/app/status.js +13 -14
  49. package/dist/commands/game/ios/app/sync.js +10 -11
  50. package/dist/commands/game/ios/profile/create.js +12 -12
  51. package/dist/commands/game/ios/profile/export.js +11 -11
  52. package/dist/commands/game/ios/profile/import.js +11 -11
  53. package/dist/commands/game/ios/profile/status.js +12 -12
  54. package/dist/commands/game/ios/status.js +16 -17
  55. package/dist/commands/game/ios/wizard.js +2 -2
  56. package/dist/commands/game/job/list.js +8 -10
  57. package/dist/commands/game/job/status.js +14 -13
  58. package/dist/commands/game/list.js +8 -10
  59. package/dist/commands/game/ship.js +2 -2
  60. package/dist/commands/game/status.js +13 -14
  61. package/dist/commands/game/wizard.js +1 -1
  62. package/dist/commands/internal/fastlane.js +1 -1
  63. package/dist/commands/internal/readme.js +1 -1
  64. package/dist/commands/login.js +2 -2
  65. package/dist/commands/status.js +11 -11
  66. package/dist/{export-B0FJT0EU.js → export-BiLHgSJ9.js} +1 -1
  67. package/dist/{import-CLDJ2iPu.js → import-CxNikF1c.js} +1 -1
  68. package/dist/index-B5XHQfs2.js +122 -0
  69. package/dist/{index-Df8uXQ4s.js → index-B6V7vGOj.js} +1 -1
  70. package/dist/{index-CF0fIsx2.js → index-BQRxiyqn.js} +1 -1
  71. package/dist/{index-CFHmtzfq.js → index-BuZmCvZh.js} +1 -1
  72. package/dist/index-CgBgZUkL.js +144 -0
  73. package/dist/{upload-C5L82Yq0.js → upload-CMo3hUhl.js} +1 -1
  74. package/dist/{useAndroidServiceAccountTestResult-BnxNuoG3.js → useAndroidServiceAccountTestResult-D_30xIJA.js} +1 -1
  75. package/dist/{useAppleApp-IXRdsK5w.js → useAppleApp-DnSjUfSs.js} +1 -1
  76. package/dist/{useAppleBundleId-DYC5ISKT.js → useAppleBundleId-BNI8swhC.js} +1 -1
  77. package/dist/useJobWatching-Bz1e6xOv.js +43 -0
  78. package/dist/useProjectCredentials-B5ZmpIxL.js +54 -0
  79. package/dist/useWebSocket-DoImIdTy.js +36 -0
  80. package/npm-shrinkwrap.json +2 -2
  81. package/oclif.manifest.json +115 -115
  82. package/package.json +3 -2
  83. package/dist/Command-BrfJSeOC.js +0 -1077
@@ -1,1077 +0,0 @@
1
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import React, { useEffect, useState, useRef, useContext } from 'react';
3
- import { Text, Box, useInput } from 'ink';
4
- import open from 'open';
5
- import { p as getAuthedHeaders, q as API_URL, Z as castObjectDates, P as Platform, a0 as getShortDateTime, J as castArrayObjectDates, O as getGoogleStatus, K as JobStatus, a1 as getShortTimeDelta, $ as getJob, b as getShortDate, a2 as queryClient, x as DEFAULT_SHIPPED_FILES_GLOBS, w as DEFAULT_IGNORED_FILES_GLOBS, z as getNewUploadTicket, E as startJobsFromUpload, a3 as getAuthToken, a4 as WS_URL, C as CredentialsType, a5 as castJobDates, H as getProject, u as getGodotVersion, t as GameEngine, v as createProject, a6 as getShortAuthRequiredUrl, a7 as getGoogleAuthUrl, W as WEB_URL } from './index-CF0fIsx2.js';
6
- import { g as getShortUUID, j as getPlatformName, c as getFileHash, s as scriptDir } from './index-Df8uXQ4s.js';
7
- import Spinner from 'ink-spinner';
8
- import axios from 'axios';
9
- import 'crypto-js';
10
- import { v4 } from 'uuid';
11
- import 'luxon';
12
- import fs__default from 'fs';
13
- import { Alert, TextInput, ProgressBar } from '@inkjs/ui';
14
- import { c as cacheKeys, u as useAndroidServiceAccountTestResult, K as KeyTestStatus, a as KeyTestError, f as fetchKeyTestResult } from './useAndroidServiceAccountTestResult-BnxNuoG3.js';
15
- import { useQuery, useMutation, useQueryClient, QueryClientProvider } from '@tanstack/react-query';
16
- import { setOptions, parse } from 'marked';
17
- import TerminalRenderer from 'marked-terminal';
18
- import qrcode from 'qrcode';
19
- import 'string-length';
20
- import 'strip-ansi';
21
- import fg from 'fast-glob';
22
- import yazl from 'yazl';
23
- import { io } from 'socket.io-client';
24
- import 'crypto';
25
- import 'readline-sync';
26
- import 'node:readline';
27
- import 'node:path';
28
- import 'node:url';
29
- import 'isomorphic-git';
30
- import '@oclif/core';
31
- import { g as getCWDGitInfo } from './git-DREGq-jc.js';
32
- import { a as getProjectCredentials } from './index-CFHmtzfq.js';
33
-
34
- async function fetchStatus({ projectId }) {
35
- try {
36
- if (!projectId) throw new Error("projectId is required");
37
- const headers = getAuthedHeaders();
38
- const url = `${API_URL}/projects/${projectId}/credentials/android/key/status/`;
39
- const response = await axios.get(url, { headers });
40
- return castObjectDates(response.data);
41
- } catch (error) {
42
- console.warn("fetchStatus Error", error);
43
- throw error;
44
- }
45
- }
46
- const useAndroidServiceAccountSetupStatus = (props) => {
47
- return useQuery({
48
- queryKey: cacheKeys.androidSetupStatus(props),
49
- queryFn: () => fetchStatus(props),
50
- // Status changes frequently, so we want to keep it fresh
51
- refetchInterval: 1e3 * 5,
52
- staleTime: 1e3 * 5
53
- });
54
- };
55
-
56
- async function queryBuilds({ projectId, ...pageAndSortParams }) {
57
- try {
58
- const headers = getAuthedHeaders();
59
- const url = `${API_URL}/projects/${projectId}/builds`;
60
- const response = await axios.get(url, { headers, params: pageAndSortParams });
61
- return {
62
- ...response.data,
63
- data: castArrayObjectDates(response.data.data)
64
- };
65
- } catch (error) {
66
- console.warn("queryBuilds Error", error);
67
- throw error;
68
- }
69
- }
70
- function getBuildSummary(build) {
71
- const filename = build.platform == Platform.IOS ? "output.ipa" : "output.aab";
72
- return {
73
- id: getShortUUID(build.id),
74
- ...getJobDetailsSummary(build.jobDetails),
75
- platform: getPlatformName(build.platform),
76
- jobId: getShortUUID(build.jobId),
77
- createdAt: getShortDateTime(build.createdAt),
78
- cmd: `shipthis game build download ${getShortUUID(build.id)} ${filename}`
79
- };
80
- }
81
- const useBuilds = (props) => {
82
- const queryResult = useQuery({
83
- queryKey: cacheKeys.builds(props),
84
- queryFn: async () => queryBuilds(props)
85
- });
86
- return queryResult;
87
- };
88
-
89
- const useGoogleStatus = () => {
90
- return useQuery({
91
- queryKey: cacheKeys.googleStatus(),
92
- queryFn: getGoogleStatus
93
- });
94
- };
95
-
96
- function getJobDetailsSummary(jobDetails) {
97
- const semanticVersion = jobDetails?.semanticVersion || "N/A";
98
- const buildNumber = jobDetails?.buildNumber || "N/A";
99
- const gitCommit = jobDetails?.gitCommitHash ? getShortUUID(jobDetails?.gitCommitHash) : "N/A";
100
- const gitBranch = jobDetails?.gitBranch || "N/A";
101
- return {
102
- version: `${semanticVersion} (${buildNumber})`,
103
- gitInfo: `${gitCommit} (${gitBranch})`
104
- };
105
- }
106
- function getJobSummary(job, timeNow) {
107
- const inProgress = ![JobStatus.COMPLETED, JobStatus.FAILED].includes(job.status);
108
- return {
109
- id: getShortUUID(job.id),
110
- ...getJobDetailsSummary(job.details),
111
- platform: getPlatformName(job.type),
112
- status: job.status,
113
- createdAt: getShortDateTime(job.createdAt),
114
- runtime: getShortTimeDelta(job.createdAt, inProgress ? timeNow : job.updatedAt)
115
- };
116
- }
117
- const useJob = (props) => {
118
- return useQuery({
119
- queryKey: cacheKeys.job(props),
120
- queryFn: () => getJob(props.jobId, props.projectId)
121
- });
122
- };
123
-
124
- async function queryJobs({ projectId, ...pageAndSortParams }) {
125
- try {
126
- const headers = getAuthedHeaders();
127
- const url = `${API_URL}/projects/${projectId}/jobs`;
128
- const response = await axios.get(url, { headers, params: pageAndSortParams });
129
- return {
130
- ...response.data,
131
- data: castArrayObjectDates(response.data.data)
132
- };
133
- } catch (error) {
134
- console.warn("queryJobs Error", error);
135
- throw error;
136
- }
137
- }
138
- const useJobs = (props) => {
139
- const queryResult = useQuery({
140
- queryKey: cacheKeys.jobs(props),
141
- queryFn: async () => queryJobs(props)
142
- });
143
- return queryResult;
144
- };
145
-
146
- async function queryProjectCredentials({
147
- projectId,
148
- ...pageAndSortParams
149
- }) {
150
- try {
151
- const headers = getAuthedHeaders();
152
- const url = `${API_URL}/projects/${projectId}/credentials`;
153
- const response = await axios.get(url, { headers, params: pageAndSortParams });
154
- return {
155
- ...response.data,
156
- data: castArrayObjectDates(response.data.data)
157
- };
158
- } catch (error) {
159
- console.warn("queryProjectCredentials Error", error);
160
- throw error;
161
- }
162
- }
163
- function getProjectCredentialSummary(credential) {
164
- return {
165
- id: getShortUUID(credential.id),
166
- type: credential.type,
167
- serial: credential.serialNumber.substring(0, 30) + (credential.serialNumber.length > 30 ? "\u2026" : ""),
168
- isActive: credential.isActive,
169
- createdAt: getShortDate(credential.createdAt)
170
- };
171
- }
172
- const useProjectCredentials = ({
173
- platform,
174
- type,
175
- ...fetchProps
176
- }) => {
177
- const queryResult = useQuery({
178
- queryKey: cacheKeys.projectCredentials(fetchProps),
179
- queryFn: async () => queryProjectCredentials(fetchProps),
180
- select: (data) => {
181
- if (!(platform || type)) return data;
182
- return {
183
- ...data,
184
- data: data.data.filter((credential) => {
185
- return (!platform || credential.platform === platform) && (!type || credential.type === type);
186
- })
187
- };
188
- }
189
- });
190
- return queryResult;
191
- };
192
-
193
- async function ship(command) {
194
- const projectConfig = await command.getProjectConfig();
195
- if (!projectConfig.project) throw new Error("No project found in project config");
196
- const shippedFilesGlobs = projectConfig.shippedFilesGlobs || DEFAULT_SHIPPED_FILES_GLOBS;
197
- const ignoredFilesGlobs = projectConfig.ignoredFilesGlobs || DEFAULT_IGNORED_FILES_GLOBS;
198
- const files = await fg(shippedFilesGlobs, { dot: true, ignore: ignoredFilesGlobs });
199
- const zipFile = new yazl.ZipFile();
200
- for (const file of files) {
201
- zipFile.addFile(file, file);
202
- }
203
- const outputZipToFile = (zip, fileName) => new Promise((resolve) => {
204
- const outputStream = fs__default.createWriteStream(fileName);
205
- zip.outputStream.pipe(outputStream).on("close", () => resolve());
206
- zip.end();
207
- });
208
- const tmpZipFile = `${process.cwd()}/shipthis-${v4()}.zip`;
209
- await outputZipToFile(zipFile, tmpZipFile);
210
- const zipBuffer = fs__default.readFileSync(tmpZipFile);
211
- const { size } = fs__default.statSync(tmpZipFile);
212
- const uploadTicket = await getNewUploadTicket(projectConfig.project.id);
213
- await axios.put(uploadTicket.url, zipBuffer, {
214
- headers: {
215
- "Content-length": size,
216
- "Content-Type": "application/zip"
217
- }
218
- });
219
- const gitInfo = await getCWDGitInfo();
220
- const zipFileMd5 = await getFileHash(tmpZipFile);
221
- const uploadDetails = {
222
- ...gitInfo,
223
- zipFileMd5
224
- };
225
- const jobs = await startJobsFromUpload(uploadTicket.id, uploadDetails);
226
- return jobs;
227
- }
228
- const useShip = () => {
229
- return useMutation({
230
- mutationFn: ship,
231
- onSuccess: async (data) => {
232
- queryClient.invalidateQueries({
233
- queryKey: cacheKeys.jobs({ projectId: data[0].project.id, pageNumber: 0 })
234
- });
235
- }
236
- });
237
- };
238
-
239
- const useInviteServiceAccount = () => {
240
- return useMutation({
241
- mutationFn: async ({ projectId, developerId }) => {
242
- try {
243
- const headers = getAuthedHeaders();
244
- const { data } = await axios.post(
245
- `${API_URL}/projects/${projectId}/credentials/android/key/invite/`,
246
- { developerId },
247
- {
248
- headers
249
- }
250
- );
251
- return data;
252
- } catch (error) {
253
- console.error("useInviteMutation Error", error);
254
- throw error;
255
- }
256
- },
257
- onSuccess: async (data) => {
258
- const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
259
- await sleep(1e3);
260
- queryClient.invalidateQueries({
261
- queryKey: cacheKeys.androidKeyTestResult({ projectId: data.projectId })
262
- });
263
- }
264
- });
265
- };
266
-
267
- function useWebSocket(listeners = []) {
268
- const log = () => {
269
- };
270
- useEffect(() => {
271
- if (listeners.length === 0) {
272
- return;
273
- }
274
- const token = getAuthToken();
275
- const socket = io(WS_URL, {
276
- auth: { token },
277
- forceNew: true
278
- });
279
- socket.on("connect", () => log());
280
- for (const listener of listeners) {
281
- const pattern = listener.getPattern();
282
- const bindSocket = (pattern2) => {
283
- const boundListener = listener.eventHandler.bind(listener, pattern2);
284
- socket.on(pattern2, boundListener);
285
- };
286
- if (Array.isArray(pattern)) {
287
- pattern.forEach(bindSocket);
288
- continue;
289
- }
290
- bindSocket(pattern);
291
- }
292
- return () => {
293
- socket.disconnect();
294
- };
295
- }, []);
296
- }
297
-
298
- const ERR_NOT_AUTHENTICATED = "You must be connected to Google to create a Service Account Key";
299
- const useHasServiceAccountKey = (projectId) => {
300
- const { data, isSuccess } = useProjectCredentials({ projectId, platform: Platform.ANDROID });
301
- return isSuccess && data.data.some((cred) => cred.isActive && cred.platform === Platform.ANDROID && cred.type == CredentialsType.KEY);
302
- };
303
- const useAndroidServiceAccount = ({ projectId, onError, onComplete }) => {
304
- const queryClient = useQueryClient();
305
- const [isStarting, setIsStarting] = useState(false);
306
- const hasServiceAccountKey = useHasServiceAccountKey(projectId);
307
- const listener = {
308
- getPattern: () => `project.${projectId}:android-setup-status`,
309
- eventHandler: async (pattern, data) => {
310
- const key = cacheKeys.androidSetupStatus({ projectId });
311
- queryClient.setQueryData(key, () => data);
312
- }
313
- };
314
- useWebSocket([listener]);
315
- const { data: setupStatus } = useAndroidServiceAccountSetupStatus({ projectId });
316
- const prevSetupStatusRef = useRef("unknown");
317
- useEffect(() => {
318
- if (["running", "queued"].includes(prevSetupStatusRef.current)) {
319
- if (setupStatus?.status === "complete") onComplete();
320
- if (setupStatus?.status === "error") onError(new Error(setupStatus.errorMessage));
321
- }
322
- prevSetupStatusRef.current = setupStatus?.status || "unknown";
323
- }, [setupStatus]);
324
- const handleStart = async () => {
325
- try {
326
- setIsStarting(true);
327
- const currentStatus = await getGoogleStatus();
328
- if (!currentStatus.isAuthenticated) throw new Error(ERR_NOT_AUTHENTICATED);
329
- const headers = getAuthedHeaders();
330
- const androidKeyApiBase = `${API_URL}/projects/${projectId}/credentials/android/key`;
331
- const startUrl = `${androidKeyApiBase}/setup/`;
332
- const { data: updatedStatus } = await axios.post(startUrl, {}, { headers });
333
- queryClient.invalidateQueries({
334
- queryKey: cacheKeys.projectCredentials({ projectId, pageNumber: 0 })
335
- });
336
- await queryClient.setQueryData(cacheKeys.androidSetupStatus({ projectId }), (_) => updatedStatus);
337
- setIsStarting(false);
338
- return true;
339
- } catch (error) {
340
- setIsStarting(false);
341
- console.warn("useAndroidServiceAccount.handleStart Error", error);
342
- onError(error);
343
- return false;
344
- }
345
- };
346
- const isCreating = isStarting || setupStatus?.status === "queued" || setupStatus?.status === "running";
347
- return {
348
- handleStart,
349
- setupStatus,
350
- isCreating,
351
- hasServiceAccountKey
352
- };
353
- };
354
-
355
- function useGoogleStatusWatching({
356
- projectId,
357
- isWatching,
358
- onGoogleStatusUpdate
359
- }) {
360
- const [wsGoogleStatus, setWsGoogleStatus] = useState(null);
361
- const listener = {
362
- getPattern: () => `project.${projectId}:google-status`,
363
- eventHandler: async (pattern, data2) => {
364
- setWsGoogleStatus(data2);
365
- if (onGoogleStatusUpdate) onGoogleStatusUpdate(data2);
366
- }
367
- };
368
- useWebSocket([listener] );
369
- const { isLoading, data: googleStatus } = useGoogleStatus();
370
- useEffect(() => {
371
- setWsGoogleStatus(null);
372
- }, [projectId, isWatching, googleStatus]);
373
- const fetchedGoogleStatus = googleStatus ? googleStatus : null;
374
- const data = wsGoogleStatus ? wsGoogleStatus : fetchedGoogleStatus;
375
- return {
376
- isLoading,
377
- data
378
- };
379
- }
380
-
381
- function useJobWatching({ projectId, jobId, isWatching, onJobUpdate }) {
382
- const [websocketJob, setWebsocketJob] = useState(null);
383
- const [mostRecentLog, setMostRecentLog] = useState(null);
384
- const jobStatusListener = {
385
- getPattern: () => [`project.${projectId}:job:created`, `project.${projectId}:job:updated`],
386
- eventHandler: async (pattern, rawJob) => {
387
- if (rawJob.id !== jobId) return;
388
- const job2 = castJobDates(rawJob);
389
- setWebsocketJob(job2);
390
- if (onJobUpdate) onJobUpdate(job2);
391
- }
392
- };
393
- const jobProgressListener = {
394
- getPattern: () => `project.${projectId}:job.${jobId}:log`,
395
- eventHandler: async (pattern, rawLogEntry) => {
396
- const logEntry = castObjectDates(rawLogEntry, ["sentAt", "createdAt"]);
397
- setMostRecentLog(logEntry);
398
- }
399
- };
400
- useWebSocket(isWatching ? [jobStatusListener, jobProgressListener] : []);
401
- const { isLoading, data: job } = useJob({
402
- projectId,
403
- jobId
404
- });
405
- useEffect(() => {
406
- setWebsocketJob(null);
407
- }, [jobId, projectId, isWatching, job]);
408
- const fetchedJob = job ? job : null;
409
- const data = websocketJob ? websocketJob : fetchedJob;
410
- const progress = mostRecentLog?.progress || null;
411
- return {
412
- isLoading,
413
- data,
414
- progress
415
- };
416
- }
417
-
418
- const CommandContext = React.createContext({
419
- command: null,
420
- setCommand: (command) => {
421
- }
422
- });
423
- const CommandProvider = (props) => {
424
- const [command, setCommand] = useState(props.command || null);
425
- return /* @__PURE__ */ jsx(CommandContext.Provider, { value: { command, setCommand }, children: props.children });
426
- };
427
-
428
- const GameContext = React.createContext({
429
- gameId: null,
430
- game: null,
431
- setGameId: (gameId) => {
432
- }
433
- });
434
- const GameProvider = ({ children }) => {
435
- const [gameId, setGameId] = useState(null);
436
- const [game, setGame] = useState(null);
437
- const { command } = React.useContext(CommandContext);
438
- const handleLoad = async () => {
439
- if (command) {
440
- const commandGameId = await command.getGameId();
441
- if (commandGameId) setGameId(commandGameId);
442
- }
443
- };
444
- const handleGameIdChange = async () => {
445
- if (!gameId) {
446
- setGame(null);
447
- return;
448
- }
449
- const game2 = await getProject(gameId);
450
- setGame(game2);
451
- };
452
- useEffect(() => {
453
- handleGameIdChange();
454
- }, [gameId]);
455
- useEffect(() => {
456
- handleLoad();
457
- }, [command]);
458
- return /* @__PURE__ */ jsx(GameContext.Provider, { value: { gameId, game, setGameId }, children });
459
- };
460
-
461
- const GameInfoForm = ({ gameInfo, onSubmit }) => {
462
- const [activeInput, setActiveInput] = useState("name");
463
- const [error, setError] = useState(null);
464
- const [name, setName] = useState(gameInfo.name);
465
- const [androidPackageName, setAndroidPackageName] = useState(gameInfo?.details?.androidPackageName);
466
- const handleSubmitName = () => {
467
- setError(null);
468
- if (name.length === 0) {
469
- setError("Please enter a name for your game");
470
- return;
471
- }
472
- setActiveInput("androidPackageName");
473
- };
474
- const handleSubmitPackageName = () => {
475
- setError(null);
476
- const packageRegex = /^[a-zA-Z][a-zA-Z0-9_]*(\.[a-zA-Z][a-zA-Z0-9_]*)+$/;
477
- if (!packageRegex.test(`${androidPackageName}`)) {
478
- setError("Please enter a valid package name e.g. com.flappy.souls");
479
- return;
480
- }
481
- onSubmit({
482
- ...gameInfo,
483
- name,
484
- details: {
485
- ...gameInfo.details,
486
- androidPackageName
487
- }
488
- });
489
- };
490
- return /* @__PURE__ */ jsxs(Fragment, { children: [
491
- /* @__PURE__ */ jsx(Text, { bold: true, children: "Please confirm the following information about your game" }),
492
- error && /* @__PURE__ */ jsx(Alert, { variant: "error", children: error }),
493
- /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [
494
- /* @__PURE__ */ jsx(
495
- FormTextInput,
496
- {
497
- label: "Game name:",
498
- isDisabled: activeInput !== "name",
499
- defaultValue: name,
500
- placeholder: "Enter the name of your game...",
501
- onChange: setName,
502
- onSubmit: handleSubmitName
503
- }
504
- ),
505
- /* @__PURE__ */ jsx(
506
- FormTextInput,
507
- {
508
- label: "Android package name :",
509
- isDisabled: activeInput !== "androidPackageName",
510
- defaultValue: androidPackageName,
511
- placeholder: "e.g. com.flappy.souls",
512
- onChange: setAndroidPackageName,
513
- onSubmit: handleSubmitPackageName
514
- }
515
- )
516
- ] })
517
- ] });
518
- };
519
-
520
- const getGameInfo = (flagValues, project) => {
521
- const androidPackageName = flagValues.androidPackageName || project?.details?.androidPackageName || "";
522
- const gameInfo = {
523
- name: project?.name || flagValues.name || "",
524
- details: {
525
- ...project?.details,
526
- androidPackageName
527
- }
528
- };
529
- return gameInfo;
530
- };
531
- const CreateGame = (props) => {
532
- const [isLoading, setIsLoading] = useState(true);
533
- const [gameInfo, setGameInfo] = useState(null);
534
- const [showForm, setShowForm] = useState(false);
535
- const { command } = useContext(CommandContext);
536
- const { setGameId, game } = useContext(GameContext);
537
- const handleLoad = async () => {
538
- if (!command) return;
539
- const flagValues = command.getDetailsFlagsValues();
540
- const projectConfig = await command.getProjectConfigSafe();
541
- if (!projectConfig.project) {
542
- setShowForm(true);
543
- setIsLoading(false);
544
- const gameInfo3 = getGameInfo(flagValues);
545
- setGameInfo(gameInfo3);
546
- return;
547
- }
548
- const gameInfo2 = getGameInfo(flagValues, game || undefined);
549
- setGameInfo(gameInfo2);
550
- setShowForm(true);
551
- setIsLoading(false);
552
- };
553
- useEffect(() => {
554
- handleLoad();
555
- }, []);
556
- const handleGameInfoSubmit = async (gameInfo2) => {
557
- if (!command) return;
558
- setShowForm(false);
559
- setIsLoading(true);
560
- const isNew = !(await command.getProjectConfigSafe()).project;
561
- if (!isNew) {
562
- const cmd = command;
563
- await cmd.updateGame(gameInfo2);
564
- return props.onComplete();
565
- }
566
- const { name, details } = gameInfo2;
567
- const projectDetails = {
568
- ...details,
569
- gameEngine: GameEngine.GODOT,
570
- gameEngineVersion: getGodotVersion()
571
- };
572
- const project = await createProject({ name, details: projectDetails });
573
- await command.setProjectConfig({
574
- project,
575
- shippedFilesGlobs: DEFAULT_SHIPPED_FILES_GLOBS,
576
- ignoredFilesGlobs: DEFAULT_IGNORED_FILES_GLOBS
577
- });
578
- setGameId(project.id);
579
- props.onComplete();
580
- };
581
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, borderStyle: "single", margin: 1, children: [
582
- isLoading && /* @__PURE__ */ jsx(Spinner, {}),
583
- showForm && gameInfo && /* @__PURE__ */ jsx(GameInfoForm, { gameInfo, onSubmit: handleGameInfoSubmit })
584
- ] });
585
- };
586
-
587
- const CreateKeystore = ({ onComplete, onError, ...boxProps }) => {
588
- const { gameId } = useContext(GameContext);
589
- const queryClient = useQueryClient();
590
- const handleCreate = async () => {
591
- try {
592
- if (!gameId) throw new Error("No game");
593
- const headers = await getAuthedHeaders();
594
- await axios.post(`${API_URL}/projects/${gameId}/credentials/android/certificate`, null, {
595
- headers
596
- });
597
- queryClient.invalidateQueries({ queryKey: cacheKeys.projectCredentials({ projectId: gameId, pageNumber: 0 }) });
598
- } catch (err) {
599
- onError(err);
600
- }
601
- };
602
- return /* @__PURE__ */ jsx(Box, { flexDirection: "column", gap: 1, ...boxProps, children: /* @__PURE__ */ jsx(
603
- RunWithSpinner,
604
- {
605
- executeMethod: handleCreate,
606
- msgInProgress: "Creating Keystore...",
607
- msgComplete: "Keystore created",
608
- onComplete
609
- }
610
- ) });
611
- };
612
-
613
- const FormTextInput = ({ label, labelProps, ...rest }) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
614
- /* @__PURE__ */ jsx(Text, { ...labelProps, children: label }),
615
- /* @__PURE__ */ jsx(TextInput, { ...rest })
616
- ] });
617
-
618
- const Title = ({ children, ...rest }) => /* @__PURE__ */ jsx(Text, { bold: true, ...rest, children: children.toUpperCase() });
619
-
620
- const Markdown = ({ path, templateVars, ...options }) => {
621
- setOptions({ renderer: new TerminalRenderer(options) });
622
- const template = fs__default.readFileSync(path, "utf8").trim();
623
- const markdown = !templateVars ? template : template.replace(/\${(.*?)}/g, (_, key) => templateVars[key.trim()] || "");
624
- return /* @__PURE__ */ jsx(Text, { children: parse(markdown).trim() });
625
- };
626
-
627
- const ProgressSpinner = ({ progress, label, spinnerType, labelProps, boxProps }) => /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Box, { flexDirection: "column", gap: 1, ...boxProps, children: /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
628
- label && label != "" && /* @__PURE__ */ jsx(Text, { ...labelProps, children: label }),
629
- /* @__PURE__ */ jsx(ProgressBar, { value: progress || 0 }),
630
- /* @__PURE__ */ jsxs(Text, { children: [
631
- Math.floor(progress || 0),
632
- "%"
633
- ] }),
634
- /* @__PURE__ */ jsx(Spinner, { type: spinnerType })
635
- ] }) }) });
636
-
637
- const QRCodeTerminal = ({ url }) => {
638
- const [code, setCode] = useState(null);
639
- const handleLoad = async () => {
640
- const codeString = await qrcode.toString(url, { type: "terminal", errorCorrectionLevel: "L", small: true });
641
- setCode(codeString);
642
- };
643
- useEffect(() => {
644
- handleLoad();
645
- }, []);
646
- return /* @__PURE__ */ jsx(Fragment, { children: code && /* @__PURE__ */ jsx(Text, { children: code }) });
647
- };
648
-
649
- const RunWithSpinner = ({
650
- executeMethod,
651
- msgInProgress,
652
- msgComplete,
653
- onComplete,
654
- spinnerType
655
- }) => {
656
- const [isInProgress, setIsInProgress] = React.useState(true);
657
- React.useEffect(() => {
658
- setIsInProgress(true);
659
- executeMethod().then(() => {
660
- setIsInProgress(false);
661
- return onComplete();
662
- });
663
- }, []);
664
- return /* @__PURE__ */ jsxs(Box, { children: [
665
- /* @__PURE__ */ jsx(Text, { children: isInProgress ? msgInProgress : msgComplete }),
666
- isInProgress && /* @__PURE__ */ jsx(Spinner, { type: spinnerType })
667
- ] });
668
- };
669
-
670
- async function getConnectUrl(gameId, helpPage) {
671
- const helpPagePath = `/docs/android?gameId=${gameId}#2-connect-shipthis-with-google`;
672
- const url = helpPage ? await getShortAuthRequiredUrl(helpPagePath) : await getGoogleAuthUrl(gameId);
673
- return url;
674
- }
675
- const GoogleAuthQRCode = ({ gameId, helpPage }) => {
676
- const [url, setUrl] = useState(null);
677
- const handleLoad = async () => {
678
- const url2 = await getConnectUrl(gameId, helpPage);
679
- setUrl(url2);
680
- };
681
- useEffect(() => {
682
- handleLoad();
683
- }, []);
684
- return /* @__PURE__ */ jsx(Fragment, { children: url && /* @__PURE__ */ jsx(QRCodeTerminal, { url }) });
685
- };
686
-
687
- const __dirname$3 = scriptDir(import.meta);
688
- const ConnectGoogle = (props) => {
689
- const { gameId } = useContext(GameContext);
690
- return /* @__PURE__ */ jsx(Fragment, { children: gameId && /* @__PURE__ */ jsx(ConnectForGame, { gameId, ...props }) });
691
- };
692
- const ConnectForGame = ({ onComplete, onError, helpPage, gameId, ...boxProps }) => {
693
- useGoogleStatusWatching({
694
- projectId: gameId,
695
- isWatching: true,
696
- onGoogleStatusUpdate: (status) => {
697
- if (status.isAuthenticated) return onComplete();
698
- }
699
- });
700
- useInput(async (input) => {
701
- if (!gameId) return;
702
- if (input !== "d") return;
703
- const url = await getConnectUrl(gameId, true);
704
- await open(url);
705
- });
706
- const templateVars = {
707
- privacyURL: new URL("/privacy", WEB_URL).toString()
708
- };
709
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, ...boxProps, children: [
710
- /* @__PURE__ */ jsx(Markdown, { path: `${__dirname$3}/privacy-notification.md`, templateVars }),
711
- /* @__PURE__ */ jsx(Text, { children: "Scan the QR code below to connect your Google account to ShipThis:" }),
712
- gameId && /* @__PURE__ */ jsx(GoogleAuthQRCode, { gameId, helpPage: !!helpPage }),
713
- /* @__PURE__ */ jsx(Text, { children: "Or press D to sign-in using your browser" })
714
- ] });
715
- };
716
-
717
- const CreateServiceAccountKey = (props) => {
718
- const { gameId } = useContext(GameContext);
719
- return /* @__PURE__ */ jsx(Fragment, { children: gameId && /* @__PURE__ */ jsx(CreateForGame$1, { gameId, ...props }) });
720
- };
721
- const CreateForGame$1 = ({ onComplete, onError, gameId, ...boxProps }) => {
722
- const [didStart, setDidStart] = useState(false);
723
- const { handleStart, setupStatus, isCreating } = useAndroidServiceAccount({
724
- projectId: gameId,
725
- onError,
726
- onComplete
727
- });
728
- useEffect(() => {
729
- handleStart().then(() => setDidStart(true));
730
- }, [gameId]);
731
- return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, ...boxProps, children: [
732
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
733
- /* @__PURE__ */ jsx(Text, { children: "Creating a Service Account and API Key..." }),
734
- isCreating && /* @__PURE__ */ jsx(Spinner, { type: "dots" })
735
- ] }),
736
- didStart && /* @__PURE__ */ jsx(ProgressSpinner, { progress: (setupStatus?.progress || 0) * 100, spinnerType: "dots" })
737
- ] }) });
738
- };
739
-
740
- const JobProgress = (props) => {
741
- const prevJobStatus = useRef(props.job.status);
742
- const handleJobUpdate = (job) => {
743
- const completed = [JobStatus.COMPLETED, JobStatus.FAILED];
744
- const wasRunning = !completed.includes(prevJobStatus.current);
745
- if (completed.includes(job.status) && wasRunning) {
746
- props.onComplete();
747
- }
748
- prevJobStatus.current = job.status;
749
- };
750
- const { progress } = useJobWatching({
751
- projectId: props.job.project.id,
752
- jobId: props.job.id,
753
- isWatching: true,
754
- onJobUpdate: handleJobUpdate
755
- });
756
- return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(ProgressSpinner, { progress, label: "Job progress...", spinnerType: "dots" }) });
757
- };
758
-
759
- const CreateInitialBuild = (props) => {
760
- const { gameId } = useContext(GameContext);
761
- return /* @__PURE__ */ jsx(Fragment, { children: gameId && /* @__PURE__ */ jsx(CreateForGame, { gameId, ...props }) });
762
- };
763
- const CreateForGame = ({ onComplete, onError, gameId, ...boxProps }) => {
764
- const { command } = useContext(CommandContext);
765
- const { data: buildData, isLoading: isLoadingBuilds } = useBuilds({ projectId: gameId, pageNumber: 0 });
766
- const { data: jobData, isLoading: isLoadingJobs } = useJobs({
767
- projectId: gameId,
768
- pageNumber: 0
769
- });
770
- const prevHasBuild = useRef(false);
771
- const shipMutation = useShip();
772
- useEffect(() => {
773
- if (isLoadingBuilds || isLoadingJobs) return;
774
- if (!buildData) return;
775
- if (!jobData) return;
776
- if (!command) return;
777
- const hasAndroidBuild = buildData.data.some((build) => build.platform === Platform.ANDROID);
778
- if (!prevHasBuild.current && hasAndroidBuild) return onComplete();
779
- prevHasBuild.current = hasAndroidBuild;
780
- const hasAndroidJob = jobData.data.some((job) => job.type === Platform.ANDROID);
781
- const shouldRun = !hasAndroidBuild && !hasAndroidJob;
782
- if (shouldRun) shipMutation.mutateAsync(command).catch(onError);
783
- }, [buildData, jobData, command]);
784
- const androidJob = jobData?.data.find(
785
- (job) => job.type === Platform.ANDROID && [JobStatus.PENDING, JobStatus.PROCESSING].includes(job.status)
786
- );
787
- return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, ...boxProps, children: [
788
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
789
- /* @__PURE__ */ jsx(Text, { children: "Create an initial build..." }),
790
- (isLoadingBuilds || isLoadingJobs || shipMutation.isPending) && /* @__PURE__ */ jsx(Spinner, { type: "dots" })
791
- ] }),
792
- androidJob && /* @__PURE__ */ jsx(JobProgress, { job: androidJob, onComplete })
793
- ] }) });
794
- };
795
-
796
- const __dirname$2 = scriptDir(import.meta);
797
- const getIsAppFound = (result) => {
798
- const isFound = result?.status === KeyTestStatus.SUCCESS || result?.status === KeyTestStatus.ERROR && result?.error === KeyTestError.NOT_INVITED;
799
- return isFound;
800
- };
801
- const CreateGooglePlayGame = (props) => {
802
- const { gameId } = useContext(GameContext);
803
- return /* @__PURE__ */ jsx(Fragment, { children: gameId && /* @__PURE__ */ jsx(Create, { gameId, ...props }) });
804
- };
805
- const Create = ({ onComplete, onError, gameId, ...boxProps }) => {
806
- const { data: result, isFetching } = useAndroidServiceAccountTestResult({ projectId: gameId });
807
- const { data: builds } = useBuilds({ projectId: gameId, pageNumber: 0 });
808
- const previousIsFound = useRef(false);
809
- useEffect(() => {
810
- const isFound = getIsAppFound(result);
811
- if (previousIsFound.current === false && isFound) {
812
- onComplete();
813
- }
814
- previousIsFound.current = isFound;
815
- }, [result]);
816
- useInput(async (input) => {
817
- if (!gameId) return;
818
- switch (input) {
819
- case "r":
820
- queryClient.invalidateQueries({
821
- queryKey: cacheKeys.androidKeyTestResult({ projectId: gameId })
822
- });
823
- break;
824
- case "d":
825
- const dashUrl = await getShortAuthRequiredUrl(`/games/${getShortUUID(gameId)}/builds`);
826
- await open(dashUrl);
827
- }
828
- if (input !== "r") return;
829
- queryClient.invalidateQueries({
830
- queryKey: cacheKeys.androidKeyTestResult({ projectId: gameId })
831
- });
832
- });
833
- const initialBuild = builds?.data.find((build) => build.platform === Platform.ANDROID);
834
- const downloadCmd = initialBuild ? `${getBuildSummary(initialBuild).cmd}` : "";
835
- const templateVars = {
836
- downloadCmd,
837
- dashboardURL: new URL(`/games/${getShortUUID(gameId)}/builds`, WEB_URL).toString()
838
- };
839
- return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, ...boxProps, children: [
840
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
841
- /* @__PURE__ */ jsx(Text, { bold: true, children: isFetching ? "Checking..." : "ShipThis has not detected your game in Google Play. Press R to test again." }),
842
- isFetching && /* @__PURE__ */ jsx(Spinner, { type: "dots" })
843
- ] }),
844
- /* @__PURE__ */ jsx(Markdown, { path: `${__dirname$2}/help.md`, templateVars })
845
- ] }) });
846
- };
847
-
848
- const InviteForm = ({ onSubmit }) => {
849
- const [error, setError] = useState(null);
850
- const [accountId, setAccountId] = useState("");
851
- const handleSubmitAccountId = () => {
852
- setError(null);
853
- const idRegEx = /^\d{10,20}$/;
854
- if (!idRegEx.test(`${accountId}`)) {
855
- setError("Please enter a valid Google Play Account ID (10-20 digits)");
856
- return;
857
- }
858
- return onSubmit(accountId);
859
- };
860
- return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [
861
- /* @__PURE__ */ jsx(
862
- FormTextInput,
863
- {
864
- label: "Please enter your Google Play Account ID:",
865
- labelProps: { bold: true },
866
- defaultValue: accountId,
867
- placeholder: "e.g. 8110853839480950872",
868
- onChange: setAccountId,
869
- onSubmit: handleSubmitAccountId
870
- }
871
- ),
872
- error && /* @__PURE__ */ jsx(Alert, { variant: "error", children: error })
873
- ] }) });
874
- };
875
-
876
- const __dirname$1 = scriptDir(import.meta);
877
- const InviteServiceAccount = ({ onComplete, onError, ...boxProps }) => {
878
- const { gameId } = useContext(GameContext);
879
- const inviteMutation = useInviteServiceAccount();
880
- const handleSubmit = async (developerId) => {
881
- try {
882
- if (!gameId) return;
883
- await inviteMutation.mutateAsync({ projectId: gameId, developerId });
884
- onComplete();
885
- } catch (error) {
886
- onError(error);
887
- }
888
- };
889
- const templateVars = {
890
- guideURL: new URL("/docs/guides/google-play-account-id", WEB_URL).toString()
891
- };
892
- return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, ...boxProps, children: [
893
- /* @__PURE__ */ jsx(Markdown, { path: `${__dirname$1}/help.md`, templateVars }),
894
- /* @__PURE__ */ jsxs(Box, { children: [
895
- inviteMutation.isPending && /* @__PURE__ */ jsx(Spinner, { type: "dots" }),
896
- !inviteMutation.isPending && /* @__PURE__ */ jsx(InviteForm, { onSubmit: handleSubmit })
897
- ] })
898
- ] }) });
899
- };
900
-
901
- var StepStatus = /* @__PURE__ */ ((StepStatus2) => {
902
- StepStatus2["PENDING"] = "PENDING";
903
- StepStatus2["RUNNING"] = "RUNNING";
904
- StepStatus2["SUCCESS"] = "SUCCESS";
905
- StepStatus2["FAILURE"] = "FAILURE";
906
- StepStatus2["WARN"] = "WARN";
907
- return StepStatus2;
908
- })(StepStatus || {});
909
- const Steps = [
910
- "createGame",
911
- "createKeystore",
912
- "connectGoogle",
913
- "createServiceAccount",
914
- "createInitialBuild",
915
- "createGooglePlayGame",
916
- "inviteServiceAccount"
917
- ];
918
- const getStepInitialStatus = (step, statusFlags) => {
919
- const base = {
920
- gameInfo: statusFlags.hasGameName && statusFlags.hasAndroidPackageName,
921
- createGame: statusFlags.hasShipThisProject,
922
- createKeystore: statusFlags.hasAndroidKeystore,
923
- createServiceAccount: statusFlags.hasServiceAccountKey,
924
- createGooglePlayGame: statusFlags.hasGooglePlayGame,
925
- inviteServiceAccount: statusFlags.hasInvitedServiceAccount
926
- };
927
- if (step in base) return base[step] ? "SUCCESS" /* SUCCESS */ : "PENDING" /* PENDING */;
928
- if (step === "connectGoogle") {
929
- if (!statusFlags.hasGoogleConnection && statusFlags.hasServiceAccountKey && statusFlags.hasInvitedServiceAccount)
930
- return "WARN" /* WARN */;
931
- return statusFlags.hasGoogleConnection ? "SUCCESS" /* SUCCESS */ : "PENDING" /* PENDING */;
932
- }
933
- if (step === "createInitialBuild") {
934
- if (!statusFlags.hasInitialBuild && statusFlags.hasGooglePlayGame) return "WARN" /* WARN */;
935
- return statusFlags.hasInitialBuild ? "SUCCESS" /* SUCCESS */ : "PENDING" /* PENDING */;
936
- }
937
- throw new Error(`Unknown step: ${step}`);
938
- };
939
- const getStatusFlags = async (cmd) => {
940
- const projectConfig = await cmd.getProjectConfigSafe();
941
- const projectId = projectConfig.project?.id;
942
- const project = !!projectId && await getProject(projectId);
943
- const hasShipThisProject = !!project;
944
- const hasGameName = project && !!project?.name;
945
- const hasAndroidPackageName = project && !!project?.details?.androidPackageName;
946
- const projectCredentials = hasShipThisProject ? await getProjectCredentials(project.id) : [];
947
- const hasAndroidKeystore = projectCredentials.some(
948
- (cred) => cred.isActive && cred.platform === Platform.ANDROID && cred.type == CredentialsType.CERTIFICATE
949
- );
950
- const googleStatus = await getGoogleStatus();
951
- const hasGoogleConnection = googleStatus.isAuthenticated;
952
- const hasServiceAccountKey = projectCredentials.some(
953
- (cred) => cred.isActive && cred.platform == Platform.ANDROID && cred.type == CredentialsType.KEY
954
- );
955
- const buildsResponse = !!projectId && hasShipThisProject && await queryBuilds({ projectId, pageNumber: 0 });
956
- const hasInitialBuild = !!buildsResponse && buildsResponse.data.length > 0;
957
- const testResult = projectId ? await fetchKeyTestResult({ projectId }) : null;
958
- const hasGooglePlayGame = testResult && testResult?.status === KeyTestStatus.SUCCESS || testResult?.status === KeyTestStatus.ERROR && testResult?.error === KeyTestError.NOT_INVITED;
959
- const hasInvitedServiceAccount = testResult ? testResult?.status === KeyTestStatus.SUCCESS : false;
960
- return {
961
- hasShipThisProject,
962
- hasGameName,
963
- hasAndroidPackageName,
964
- hasAndroidKeystore,
965
- hasGoogleConnection,
966
- hasServiceAccountKey,
967
- hasInitialBuild,
968
- hasGooglePlayGame,
969
- hasInvitedServiceAccount
970
- };
971
- };
972
-
973
- const StepLabels = {
974
- createGame: "Create game in ShipThis",
975
- createKeystore: "Create an Android Keystore",
976
- connectGoogle: "Connect ShipThis with Google",
977
- createServiceAccount: "Create a Service Account & API Key",
978
- createInitialBuild: "Create an initial build",
979
- createGooglePlayGame: "Create the game in Google Play",
980
- inviteServiceAccount: "Invite the Service Account"
981
- };
982
- const StepWithStatus = ({ position, title, status }) => {
983
- const indicator = {
984
- [StepStatus.PENDING]: " ",
985
- // double space
986
- [StepStatus.RUNNING]: /* @__PURE__ */ jsxs(Fragment, { children: [
987
- /* @__PURE__ */ jsx(Spinner, { type: "dots" }),
988
- " "
989
- ] }),
990
- [StepStatus.SUCCESS]: "\u2705",
991
- // this is 2 wide?
992
- [StepStatus.FAILURE]: "\u274C",
993
- // this is 2 wide?
994
- [StepStatus.WARN]: "\u26A0\uFE0F "
995
- // double
996
- }[status];
997
- const isBold = status !== StepStatus.PENDING;
998
- return /* @__PURE__ */ jsxs(Text, { bold: isBold, children: [
999
- /* @__PURE__ */ jsx(Fragment, { children: indicator }),
1000
- " ",
1001
- position,
1002
- ". ",
1003
- title
1004
- ] });
1005
- };
1006
- const StepStatusTable = ({ stepStatuses }) => {
1007
- return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginLeft: 1, children: Steps.map((step, index) => {
1008
- return /* @__PURE__ */ jsx(StepWithStatus, { position: index + 1, title: StepLabels[step], status: stepStatuses[index] }, step);
1009
- }) });
1010
- };
1011
-
1012
- const stepComponentMap = {
1013
- createGame: CreateGame,
1014
- createKeystore: CreateKeystore,
1015
- connectGoogle: ConnectGoogle,
1016
- createServiceAccount: CreateServiceAccountKey,
1017
- createInitialBuild: CreateInitialBuild,
1018
- createGooglePlayGame: CreateGooglePlayGame,
1019
- inviteServiceAccount: InviteServiceAccount
1020
- };
1021
- const __dirname = scriptDir(import.meta);
1022
- const ON_COMPLETE_DELAY_MS = 500;
1023
- const AndroidWizard = (props) => {
1024
- const { command } = React.useContext(CommandContext);
1025
- const [currentStep, setCurrentStep] = useState(null);
1026
- const [stepStatuses, setStepStatuses] = useState(null);
1027
- const [showSuccess, setShowSuccess] = useState(false);
1028
- const determineStep = async () => {
1029
- if (!command) return;
1030
- const statusFlags = await getStatusFlags(command);
1031
- const initStatuses = Steps.map((step) => getStepInitialStatus(step, statusFlags));
1032
- const firstPending = initStatuses.findIndex((status) => status === StepStatus.PENDING);
1033
- const pendingStep = firstPending === -1 ? null : Steps[firstPending];
1034
- const withPending = initStatuses.map((status, index) => {
1035
- if (index === firstPending) return StepStatus.RUNNING;
1036
- return status;
1037
- });
1038
- setCurrentStep(pendingStep);
1039
- setStepStatuses(withPending);
1040
- const isAllDone = firstPending === -1;
1041
- setShowSuccess(isAllDone);
1042
- if (isAllDone) setTimeout(props.onComplete, ON_COMPLETE_DELAY_MS);
1043
- };
1044
- useEffect(() => {
1045
- determineStep().catch(props.onError);
1046
- }, [command]);
1047
- const handleStepComplete = () => determineStep().catch(props.onError);
1048
- const StepInterface = currentStep ? stepComponentMap[currentStep] : null;
1049
- const templateVars = {
1050
- iosSetupURL: new URL("/docs/ios", WEB_URL).toString(),
1051
- docsURL: new URL("/docs", WEB_URL).toString()
1052
- };
1053
- return /* @__PURE__ */ jsxs(GameProvider, { children: [
1054
- /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
1055
- /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Title, { children: "ShipThis Android Wizard" }) }),
1056
- stepStatuses && /* @__PURE__ */ jsx(StepStatusTable, { stepStatuses })
1057
- ] }),
1058
- StepInterface && /* @__PURE__ */ jsx(
1059
- StepInterface,
1060
- {
1061
- onComplete: handleStepComplete,
1062
- onError: props.onError,
1063
- margin: 1,
1064
- borderStyle: "single",
1065
- padding: 1
1066
- }
1067
- ),
1068
- showSuccess && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Markdown, { path: `${__dirname}/success.md`, templateVars }) })
1069
- ] });
1070
- };
1071
-
1072
- const Command = ({ children, command }) => {
1073
- const width = Math.min(160, process.stdout.columns || 80);
1074
- return /* @__PURE__ */ jsx(QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsx(CommandProvider, { command, children: /* @__PURE__ */ jsx(Box, { width, flexDirection: "column", children }) }) });
1075
- };
1076
-
1077
- export { AndroidWizard as A, Command as C, GameProvider as G, RunWithSpinner as R, Title as T, getProjectCredentialSummary as a, CreateKeystore as b, CreateServiceAccountKey as c, ConnectGoogle as d, useBuilds as e, getBuildSummary as f, getJobSummary as g, useWebSocket as h, useJobWatching as i, useProjectCredentials as u };