shipthis 0.1.10 → 0.1.11

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 (53) hide show
  1. package/assets/markdown/create-or-import-keystore.md +7 -0
  2. package/dist/AppleBundleIdDetails-DymjrhOZ.js +73 -0
  3. package/dist/Command-BiB9MqbJ.js +204 -0
  4. package/dist/CommandGame-C1oTlfdb.js +8 -0
  5. package/dist/Create-DIaSKUpl.js +56 -0
  6. package/dist/Import-BIOsf8AA.js +107 -0
  7. package/dist/JobProgress-W0QQR49T.js +108 -0
  8. package/dist/JobStatusTable-DqoppRro.js +75 -0
  9. package/dist/commands/apple/apiKey/create.js +4 -4
  10. package/dist/commands/apple/apiKey/export.js +3 -3
  11. package/dist/commands/apple/apiKey/import.js +4 -4
  12. package/dist/commands/apple/apiKey/status.js +3 -3
  13. package/dist/commands/apple/certificate/create.js +4 -4
  14. package/dist/commands/apple/certificate/export.js +3 -3
  15. package/dist/commands/apple/certificate/import.js +4 -4
  16. package/dist/commands/apple/certificate/status.js +3 -3
  17. package/dist/commands/apple/status.js +3 -3
  18. package/dist/commands/game/android/apiKey/connect.js +6 -6
  19. package/dist/commands/game/android/apiKey/create.js +6 -6
  20. package/dist/commands/game/android/apiKey/export.js +3 -3
  21. package/dist/commands/game/android/apiKey/import.js +4 -4
  22. package/dist/commands/game/android/apiKey/status.js +3 -3
  23. package/dist/commands/game/android/keyStore/create.js +7 -7
  24. package/dist/commands/game/android/keyStore/export.js +3 -3
  25. package/dist/commands/game/android/keyStore/import.js +45 -25
  26. package/dist/commands/game/android/keyStore/status.js +3 -3
  27. package/dist/commands/game/android/status.js +5 -5
  28. package/dist/commands/game/build/download.js +4 -4
  29. package/dist/commands/game/build/list.js +4 -4
  30. package/dist/commands/game/details.js +3 -3
  31. package/dist/commands/game/ios/app/addTester.js +3 -3
  32. package/dist/commands/game/ios/app/create.js +3 -3
  33. package/dist/commands/game/ios/app/status.js +4 -4
  34. package/dist/commands/game/ios/app/sync.js +3 -3
  35. package/dist/commands/game/ios/profile/create.js +4 -4
  36. package/dist/commands/game/ios/profile/export.js +3 -3
  37. package/dist/commands/game/ios/profile/import.js +4 -4
  38. package/dist/commands/game/ios/profile/status.js +3 -3
  39. package/dist/commands/game/ios/status.js +6 -6
  40. package/dist/commands/game/job/list.js +5 -5
  41. package/dist/commands/game/job/status.js +7 -7
  42. package/dist/commands/game/list.js +5 -5
  43. package/dist/commands/game/ship.js +7 -7
  44. package/dist/commands/game/status.js +5 -5
  45. package/dist/commands/game/wizard.js +116 -17
  46. package/dist/commands/status.js +3 -3
  47. package/dist/import-v54PM_Qg.js +47 -0
  48. package/dist/index-D6BH5UAM.js +135 -0
  49. package/dist/index-o9Y-84Rj.js +122 -0
  50. package/dist/useJobWatching-I_A3b36f.js +45 -0
  51. package/npm-shrinkwrap.json +2 -2
  52. package/oclif.manifest.json +27 -4
  53. package/package.json +1 -1
@@ -1,35 +1,36 @@
1
1
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
2
2
  import { Args } from '@oclif/core';
3
- import { Text, Box, render } from 'ink';
3
+ import { Text, Box, useInput, render } from 'ink';
4
4
  import { p as getAuthedHeaders, q as API_URL, K as castArrayObjectDates, $ as queryClient, a8 as updateProject, u as getGodotVersion, t as GameEngine, v as createProject, w as DEFAULT_IGNORED_FILES_GLOBS, x as DEFAULT_SHIPPED_FILES_GLOBS, P as Platform, J as JobStatus, X as WEB_URL, H as getProject, C as CredentialsType, Q as getGoogleStatus, B as BaseAuthenticatedCommand, j as isCWDGodotGame } from '../../index-BB7X1Pqp.js';
5
5
  import React, { useState, useContext, useEffect, useRef } from 'react';
6
- import { c as CommandContext, b as GameContext, d as useBuilds, M as Markdown, q as queryBuilds, G as GameProvider, e as CreateGooglePlayGame, C as Command } from '../../Command-D6rWEZRM.js';
6
+ import { c as CommandContext, b as GameContext, M as Markdown, d as useBuilds, q as queryBuilds, G as GameProvider, e as CreateGooglePlayGame, C as Command } from '../../Command-BiB9MqbJ.js';
7
7
  import Spinner from 'ink-spinner';
8
8
  import { Alert, TextInput } from '@inkjs/ui';
9
9
  import axios from 'axios';
10
10
  import 'crypto-js';
11
11
  import 'uuid';
12
12
  import 'luxon';
13
- import 'fs';
13
+ import fs__default from 'fs';
14
14
  import { useQuery, useMutation } from '@tanstack/react-query';
15
- import 'open';
15
+ import 'yazl';
16
16
  import 'crypto';
17
17
  import 'readline-sync';
18
18
  import 'node:readline';
19
19
  import 'node:path';
20
20
  import 'node:url';
21
21
  import 'fast-glob';
22
- import 'yazl';
23
22
  import 'socket.io-client';
24
23
  import 'isomorphic-git';
24
+ import 'open';
25
25
  import 'qrcode';
26
26
  import 'string-length';
27
27
  import 'strip-ansi';
28
- import { C as CreateKeystore } from '../../CreateKeystore-g1z6DsU5.js';
29
- import { C as ConnectGoogle } from '../../index-DgbQBx6x.js';
30
- import { C as CreateServiceAccountKey } from '../../index-m4HTrJ5J.js';
28
+ import { C as CreateKeystore } from '../../Create-DIaSKUpl.js';
29
+ import { I as ImportKeystore } from '../../Import-BIOsf8AA.js';
30
+ import { C as ConnectGoogle } from '../../index-o9Y-84Rj.js';
31
+ import { C as CreateServiceAccountKey } from '../../index-D6BH5UAM.js';
31
32
  import { c as cacheKeys, f as fetchKeyTestResult, K as KeyTestStatus, a as KeyTestError } from '../../useAndroidServiceAccountTestResult-Ce1x0Eh8.js';
32
- import { u as useShip, J as JobProgress } from '../../JobProgress-jmGAzvxS.js';
33
+ import { u as useShip, J as JobProgress } from '../../JobProgress-W0QQR49T.js';
33
34
  import { a as getProjectCredentials } from '../../index-DOgF4dFK.js';
34
35
  import { T as Title } from '../../Title-BCQtayg6.js';
35
36
  import 'path';
@@ -40,11 +41,12 @@ import '../../index-Cz_KLwWf.js';
40
41
  import 'marked';
41
42
  import 'marked-terminal';
42
43
  import '../../RunWithSpinner-BVXNWGD3.js';
44
+ import '../../import-v54PM_Qg.js';
43
45
  import '../../useWebSocket-CBqsjHbt.js';
44
46
  import '../../useProjectCredentials-vjedBbKl.js';
45
47
  import '../../ProgressSpinner-6pw1T8Iw.js';
46
48
  import '../../git-DREGq-jc.js';
47
- import '../../useJobWatching-D-YzSlK8.js';
49
+ import '../../useJobWatching-I_A3b36f.js';
48
50
 
49
51
  async function queryJobs({ projectId, ...pageAndSortParams }) {
50
52
  try {
@@ -230,6 +232,103 @@ const FormTextInput = ({ label, labelProps, ...rest }) => /* @__PURE__ */ jsxs(B
230
232
  /* @__PURE__ */ jsx(TextInput, { ...rest })
231
233
  ] });
232
234
 
235
+ const ImportForm = ({ importKeystoreProps, onSubmit }) => {
236
+ const [activeInput, setActiveInput] = useState("jksFilePath" /* jksFilePath */);
237
+ const [error, setError] = useState(null);
238
+ const [jksFilePath, setJksFilePath] = useState(importKeystoreProps.jksFilePath);
239
+ const [password, setPassword] = useState(importKeystoreProps.keyPassword);
240
+ const handleSubmitJksFilePath = () => {
241
+ setError(null);
242
+ if (!jksFilePath || jksFilePath.length === 0) {
243
+ setError("Please enter a path to your jks file");
244
+ return;
245
+ }
246
+ if (!fs__default.existsSync(jksFilePath)) {
247
+ setError("The file does not exist");
248
+ return;
249
+ }
250
+ setActiveInput("password" /* password */);
251
+ };
252
+ const handleSubmitPassword = () => {
253
+ setError(null);
254
+ if (!password || password.length === 0) {
255
+ setError("Please enter a password");
256
+ return;
257
+ }
258
+ onSubmit({
259
+ ...importKeystoreProps,
260
+ jksFilePath,
261
+ keyPassword: password,
262
+ keystorePassword: password
263
+ });
264
+ };
265
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
266
+ error && /* @__PURE__ */ jsx(Alert, { variant: "error", children: error }),
267
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [
268
+ /* @__PURE__ */ jsx(
269
+ FormTextInput,
270
+ {
271
+ label: "Path to your jks file:",
272
+ isDisabled: activeInput !== "jksFilePath" /* jksFilePath */,
273
+ defaultValue: jksFilePath,
274
+ placeholder: "Enter the path to your jks file...",
275
+ onChange: setJksFilePath,
276
+ onSubmit: handleSubmitJksFilePath
277
+ }
278
+ ),
279
+ /* @__PURE__ */ jsx(
280
+ FormTextInput,
281
+ {
282
+ label: "Password:",
283
+ isDisabled: activeInput !== "password" /* password */,
284
+ defaultValue: password,
285
+ placeholder: "Enter the password for your jks file...",
286
+ onChange: setPassword,
287
+ onSubmit: handleSubmitPassword
288
+ }
289
+ )
290
+ ] })
291
+ ] });
292
+ };
293
+
294
+ const CreateOrImport = ({ onComplete, onError, ...boxProps }) => {
295
+ const [stage, setStage] = useState(0 /* Choose */);
296
+ const [importKeystoreProps, setImportKeystoreProps] = useState({
297
+ jksFilePath: "",
298
+ keyPassword: "",
299
+ keystorePassword: ""
300
+ });
301
+ useInput(async (input) => {
302
+ if (stage !== 0 /* Choose */) return;
303
+ if (input === "c") return setStage(1 /* Create */);
304
+ if (input === "i") return setStage(2 /* ImportForm */);
305
+ });
306
+ const handleImportFormSubmit = (newImportProps) => {
307
+ setImportKeystoreProps(newImportProps);
308
+ setStage(3 /* ImportKeystore */);
309
+ };
310
+ const renderStage = () => {
311
+ switch (stage) {
312
+ case 0 /* Choose */:
313
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
314
+ /* @__PURE__ */ jsx(Text, { children: "Would you like to create a new keystore or import an existing one?" }),
315
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Press C to create a new keystore" }),
316
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Press I to import an existing keystore" })
317
+ ] });
318
+ case 1 /* Create */:
319
+ return /* @__PURE__ */ jsx(CreateKeystore, { onComplete, onError });
320
+ case 2 /* ImportForm */:
321
+ return /* @__PURE__ */ jsx(ImportForm, { onSubmit: handleImportFormSubmit, importKeystoreProps });
322
+ case 3 /* ImportKeystore */:
323
+ return /* @__PURE__ */ jsx(ImportKeystore, { onComplete, onError, importKeystoreProps });
324
+ }
325
+ };
326
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, ...boxProps, children: [
327
+ /* @__PURE__ */ jsx(Markdown, { filename: "create-or-import-keystore.md", templateVars: {} }),
328
+ renderStage()
329
+ ] });
330
+ };
331
+
233
332
  const CreateInitialBuild = (props) => {
234
333
  const { gameId } = useContext(GameContext);
235
334
  return /* @__PURE__ */ jsx(Fragment, { children: gameId && /* @__PURE__ */ jsx(CreateForGame, { gameId, ...props }) });
@@ -252,8 +351,10 @@ const CreateForGame = ({ onComplete, onError, gameId, ...boxProps }) => {
252
351
  const hasAndroidBuild = buildData.data.some((build) => build.platform === Platform.ANDROID);
253
352
  if (!prevHasBuild.current && hasAndroidBuild) return onComplete();
254
353
  prevHasBuild.current = hasAndroidBuild;
255
- const hasAndroidJob = jobData.data.some((job) => job.type === Platform.ANDROID);
256
- const shouldRun = !hasAndroidBuild && !hasAndroidJob;
354
+ const hasRunningAndroidJob = jobData.data.some(
355
+ (job) => job.type === Platform.ANDROID && [JobStatus.PENDING, JobStatus.PROCESSING].includes(job.status)
356
+ );
357
+ const shouldRun = !hasAndroidBuild && !hasRunningAndroidJob;
257
358
  if (shouldRun)
258
359
  shipMutation.mutateAsync({
259
360
  command,
@@ -357,9 +458,7 @@ const getStepInitialStatus = (step, statusFlags) => {
357
458
  createKeystore: statusFlags.hasAndroidKeystore,
358
459
  createServiceAccount: statusFlags.hasServiceAccountKey,
359
460
  createGooglePlayGame: statusFlags.hasGooglePlayGame,
360
- inviteServiceAccount: statusFlags.hasInvitedServiceAccount,
361
- connectGoogle: false,
362
- createInitialBuild: false
461
+ inviteServiceAccount: statusFlags.hasInvitedServiceAccount
363
462
  };
364
463
  return base[step] ? "SUCCESS" /* SUCCESS */ : "PENDING" /* PENDING */;
365
464
  };
@@ -399,7 +498,7 @@ const getStatusFlags = async (cmd) => {
399
498
 
400
499
  const StepLabels = {
401
500
  createGame: "Create game in ShipThis",
402
- createKeystore: "Create an Android Keystore",
501
+ createKeystore: "Create or import an Android Keystore",
403
502
  connectGoogle: "Connect ShipThis with Google",
404
503
  createServiceAccount: "Create a Service Account & API Key",
405
504
  createInitialBuild: "Create an initial build",
@@ -438,7 +537,7 @@ const StepStatusTable = ({ stepStatuses }) => {
438
537
 
439
538
  const stepComponentMap = {
440
539
  createGame: CreateGame,
441
- createKeystore: CreateKeystore,
540
+ createKeystore: CreateOrImport,
442
541
  connectGoogle: ConnectGoogle,
443
542
  createServiceAccount: CreateServiceAccountKey,
444
543
  createInitialBuild: CreateInitialBuild,
@@ -2,7 +2,7 @@ import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { render } from 'ink';
3
3
  import { T as BaseCommand, j as isCWDGodotGame } from '../index-BB7X1Pqp.js';
4
4
  import 'react';
5
- import { C as Command } from '../Command-D6rWEZRM.js';
5
+ import { C as Command } from '../Command-BiB9MqbJ.js';
6
6
  import 'ink-spinner';
7
7
  import 'axios';
8
8
  import 'crypto-js';
@@ -11,17 +11,17 @@ import 'luxon';
11
11
  import 'fs';
12
12
  import '@inkjs/ui';
13
13
  import '@tanstack/react-query';
14
- import 'open';
14
+ import 'yazl';
15
15
  import 'crypto';
16
16
  import 'readline-sync';
17
17
  import 'node:readline';
18
18
  import 'node:path';
19
19
  import 'node:url';
20
20
  import 'fast-glob';
21
- import 'yazl';
22
21
  import 'socket.io-client';
23
22
  import { i as isCWDGitRepo } from '../git-DREGq-jc.js';
24
23
  import '@oclif/core';
24
+ import 'open';
25
25
  import { N as NextSteps } from '../NextSteps-CK9zHOCt.js';
26
26
  import 'qrcode';
27
27
  import { S as StatusTable } from '../StatusTable-Dm5St4g-.js';
@@ -0,0 +1,47 @@
1
+ import axios from 'axios';
2
+ import { promises } from 'fs';
3
+ import { p as getAuthedHeaders, q as API_URL } from './index-BB7X1Pqp.js';
4
+
5
+ async function getNewImportTicket(projectId) {
6
+ const url = projectId ? `${API_URL}/projects/${projectId}/credentials/import/url` : `${API_URL}/credentials/import/url`;
7
+ const headers = getAuthedHeaders();
8
+ const { data: importInfo } = await axios({
9
+ method: "post",
10
+ url,
11
+ headers
12
+ });
13
+ return importInfo;
14
+ }
15
+ async function importCredential({
16
+ projectId,
17
+ zipPath,
18
+ type,
19
+ platform
20
+ }) {
21
+ const importTicket = await getNewImportTicket(projectId);
22
+ const zipBuffer = await promises.readFile(zipPath);
23
+ await axios.put(importTicket.url, zipBuffer, {
24
+ headers: {
25
+ "Content-length": zipBuffer.length,
26
+ "Content-Type": "application/zip"
27
+ }
28
+ });
29
+ const headers = getAuthedHeaders();
30
+ const url = projectId ? `${API_URL}/projects/${projectId}/credentials/import` : `${API_URL}/credentials/import`;
31
+ const { data: publicCredential } = await axios({
32
+ method: "post",
33
+ url,
34
+ headers,
35
+ data: {
36
+ uuid: importTicket.uuid,
37
+ type,
38
+ platform
39
+ }
40
+ });
41
+ if (projectId) {
42
+ return publicCredential;
43
+ }
44
+ return publicCredential;
45
+ }
46
+
47
+ export { importCredential as i };
@@ -0,0 +1,135 @@
1
+ import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
2
+ import { Box, Text } from 'ink';
3
+ import { useState, useRef, useEffect, useContext } from 'react';
4
+ import Spinner from 'ink-spinner';
5
+ import { b as GameContext } from './Command-BiB9MqbJ.js';
6
+ import { p as getAuthedHeaders, q as API_URL, F as castObjectDates, P as Platform, C as CredentialsType, Q as getGoogleStatus } from './index-BB7X1Pqp.js';
7
+ import axios from 'axios';
8
+ import 'crypto-js';
9
+ import 'uuid';
10
+ import 'luxon';
11
+ import 'fs';
12
+ import '@inkjs/ui';
13
+ import { useQuery, useQueryClient } from '@tanstack/react-query';
14
+ import 'yazl';
15
+ import 'crypto';
16
+ import 'readline-sync';
17
+ import 'node:readline';
18
+ import 'node:path';
19
+ import 'node:url';
20
+ import { c as cacheKeys } from './useAndroidServiceAccountTestResult-Ce1x0Eh8.js';
21
+ import 'fast-glob';
22
+ import 'socket.io-client';
23
+ import 'isomorphic-git';
24
+ import '@oclif/core';
25
+ import { u as useWebSocket } from './useWebSocket-CBqsjHbt.js';
26
+ import { u as useProjectCredentials } from './useProjectCredentials-vjedBbKl.js';
27
+ import 'open';
28
+ import { P as ProgressSpinner } from './ProgressSpinner-6pw1T8Iw.js';
29
+ import 'qrcode';
30
+ import 'string-length';
31
+ import 'strip-ansi';
32
+
33
+ async function fetchStatus({ projectId }) {
34
+ try {
35
+ if (!projectId) throw new Error("projectId is required");
36
+ const headers = getAuthedHeaders();
37
+ const url = `${API_URL}/projects/${projectId}/credentials/android/key/status/`;
38
+ const response = await axios.get(url, { headers });
39
+ return castObjectDates(response.data);
40
+ } catch (error) {
41
+ console.warn("fetchStatus Error", error);
42
+ throw error;
43
+ }
44
+ }
45
+ const useAndroidServiceAccountSetupStatus = (props) => {
46
+ return useQuery({
47
+ queryKey: cacheKeys.androidSetupStatus(props),
48
+ queryFn: () => fetchStatus(props),
49
+ // Status changes frequently, so we want to keep it fresh
50
+ refetchInterval: 1e3 * 5,
51
+ staleTime: 1e3 * 5
52
+ });
53
+ };
54
+
55
+ const ERR_NOT_AUTHENTICATED = "You must be connected to Google to create a Service Account Key";
56
+ const useHasServiceAccountKey = (projectId) => {
57
+ const { data, isSuccess } = useProjectCredentials({ projectId, platform: Platform.ANDROID });
58
+ return isSuccess && data.data.some((cred) => cred.isActive && cred.platform === Platform.ANDROID && cred.type == CredentialsType.KEY);
59
+ };
60
+ const useAndroidServiceAccount = ({ projectId, onError, onComplete }) => {
61
+ const queryClient = useQueryClient();
62
+ const [isStarting, setIsStarting] = useState(false);
63
+ const hasServiceAccountKey = useHasServiceAccountKey(projectId);
64
+ const listener = {
65
+ getPattern: () => `project.${projectId}:android-setup-status`,
66
+ eventHandler: async (pattern, data) => {
67
+ const key = cacheKeys.androidSetupStatus({ projectId });
68
+ queryClient.setQueryData(key, () => data);
69
+ }
70
+ };
71
+ useWebSocket([listener]);
72
+ const { data: setupStatus } = useAndroidServiceAccountSetupStatus({ projectId });
73
+ const prevSetupStatusRef = useRef("unknown");
74
+ useEffect(() => {
75
+ if (["running", "queued"].includes(prevSetupStatusRef.current)) {
76
+ if (setupStatus?.status === "complete") onComplete();
77
+ if (setupStatus?.status === "error") onError(new Error(setupStatus.errorMessage));
78
+ }
79
+ prevSetupStatusRef.current = setupStatus?.status || "unknown";
80
+ }, [setupStatus]);
81
+ const handleStart = async () => {
82
+ try {
83
+ setIsStarting(true);
84
+ const currentStatus = await getGoogleStatus();
85
+ if (!currentStatus.isAuthenticated) throw new Error(ERR_NOT_AUTHENTICATED);
86
+ const headers = getAuthedHeaders();
87
+ const androidKeyApiBase = `${API_URL}/projects/${projectId}/credentials/android/key`;
88
+ const startUrl = `${androidKeyApiBase}/setup/`;
89
+ const { data: updatedStatus } = await axios.post(startUrl, {}, { headers });
90
+ queryClient.invalidateQueries({
91
+ queryKey: cacheKeys.projectCredentials({ projectId, pageNumber: 0 })
92
+ });
93
+ await queryClient.setQueryData(cacheKeys.androidSetupStatus({ projectId }), (_) => updatedStatus);
94
+ setIsStarting(false);
95
+ return true;
96
+ } catch (error) {
97
+ setIsStarting(false);
98
+ console.warn("useAndroidServiceAccount.handleStart Error", error);
99
+ onError(error);
100
+ return false;
101
+ }
102
+ };
103
+ const isCreating = isStarting || setupStatus?.status === "queued" || setupStatus?.status === "running";
104
+ return {
105
+ handleStart,
106
+ setupStatus,
107
+ isCreating,
108
+ hasServiceAccountKey
109
+ };
110
+ };
111
+
112
+ const CreateServiceAccountKey = (props) => {
113
+ const { gameId } = useContext(GameContext);
114
+ return /* @__PURE__ */ jsx(Fragment, { children: gameId && /* @__PURE__ */ jsx(CreateForGame, { gameId, ...props }) });
115
+ };
116
+ const CreateForGame = ({ onComplete, onError, gameId, ...boxProps }) => {
117
+ const [didStart, setDidStart] = useState(false);
118
+ const { handleStart, setupStatus, isCreating } = useAndroidServiceAccount({
119
+ projectId: gameId,
120
+ onError,
121
+ onComplete
122
+ });
123
+ useEffect(() => {
124
+ handleStart().then(() => setDidStart(true));
125
+ }, [gameId]);
126
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, ...boxProps, children: [
127
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
128
+ /* @__PURE__ */ jsx(Text, { children: "Creating a Service Account and API Key..." }),
129
+ isCreating && /* @__PURE__ */ jsx(Spinner, { type: "dots" })
130
+ ] }),
131
+ didStart && /* @__PURE__ */ jsx(ProgressSpinner, { progress: (setupStatus?.progress || 0) * 100, spinnerType: "dots" })
132
+ ] }) });
133
+ };
134
+
135
+ export { CreateServiceAccountKey as C };
@@ -0,0 +1,122 @@
1
+ import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
2
+ import { Text, useInput, Box } from 'ink';
3
+ import open from 'open';
4
+ import { useState, useEffect, useContext } from 'react';
5
+ import { Q as getGoogleStatus, a2 as getShortAuthRequiredUrl, a3 as getGoogleAuthUrl, X as WEB_URL } from './index-BB7X1Pqp.js';
6
+ import 'crypto';
7
+ import 'fs';
8
+ import 'readline-sync';
9
+ import 'node:readline';
10
+ import 'node:path';
11
+ import 'node:url';
12
+ import 'axios';
13
+ import { useQuery } from '@tanstack/react-query';
14
+ import 'crypto-js';
15
+ import 'uuid';
16
+ import 'luxon';
17
+ import 'fast-glob';
18
+ import 'yazl';
19
+ import 'socket.io-client';
20
+ import { u as useWebSocket } from './useWebSocket-CBqsjHbt.js';
21
+ import { c as cacheKeys } from './useAndroidServiceAccountTestResult-Ce1x0Eh8.js';
22
+ import 'isomorphic-git';
23
+ import '@oclif/core';
24
+ import { b as GameContext, M as Markdown } from './Command-BiB9MqbJ.js';
25
+ import 'ink-spinner';
26
+ import '@inkjs/ui';
27
+ import 'string-length';
28
+ import 'strip-ansi';
29
+ import qrcode from 'qrcode';
30
+
31
+ const useGoogleStatus = () => {
32
+ return useQuery({
33
+ queryKey: cacheKeys.googleStatus(),
34
+ queryFn: getGoogleStatus
35
+ });
36
+ };
37
+
38
+ function useGoogleStatusWatching({
39
+ projectId,
40
+ isWatching,
41
+ onGoogleStatusUpdate
42
+ }) {
43
+ const [wsGoogleStatus, setWsGoogleStatus] = useState(null);
44
+ const listener = {
45
+ getPattern: () => `project.${projectId}:google-status`,
46
+ eventHandler: async (pattern, data2) => {
47
+ setWsGoogleStatus(data2);
48
+ if (onGoogleStatusUpdate) onGoogleStatusUpdate(data2);
49
+ }
50
+ };
51
+ useWebSocket([listener] );
52
+ const { isLoading, data: googleStatus } = useGoogleStatus();
53
+ useEffect(() => {
54
+ setWsGoogleStatus(null);
55
+ }, [projectId, isWatching, googleStatus]);
56
+ const fetchedGoogleStatus = googleStatus ? googleStatus : null;
57
+ const data = wsGoogleStatus ? wsGoogleStatus : fetchedGoogleStatus;
58
+ return {
59
+ isLoading,
60
+ data
61
+ };
62
+ }
63
+
64
+ const QRCodeTerminal = ({ url }) => {
65
+ const [code, setCode] = useState(null);
66
+ const handleLoad = async () => {
67
+ const codeString = await qrcode.toString(url, { type: "terminal", errorCorrectionLevel: "L", small: true });
68
+ setCode(codeString);
69
+ };
70
+ useEffect(() => {
71
+ handleLoad();
72
+ }, []);
73
+ return /* @__PURE__ */ jsx(Fragment, { children: code && /* @__PURE__ */ jsx(Text, { children: code }) });
74
+ };
75
+
76
+ async function getConnectUrl(gameId, helpPage) {
77
+ const helpPagePath = `/docs/android?gameId=${gameId}#2-connect-shipthis-with-google`;
78
+ const url = helpPage ? await getShortAuthRequiredUrl(helpPagePath) : await getGoogleAuthUrl(gameId);
79
+ return url;
80
+ }
81
+ const GoogleAuthQRCode = ({ gameId, helpPage }) => {
82
+ const [url, setUrl] = useState(null);
83
+ const handleLoad = async () => {
84
+ const url2 = await getConnectUrl(gameId, helpPage);
85
+ setUrl(url2);
86
+ };
87
+ useEffect(() => {
88
+ handleLoad();
89
+ }, []);
90
+ return /* @__PURE__ */ jsx(Fragment, { children: url && /* @__PURE__ */ jsx(QRCodeTerminal, { url }) });
91
+ };
92
+
93
+ const ConnectGoogle = (props) => {
94
+ const { gameId } = useContext(GameContext);
95
+ return /* @__PURE__ */ jsx(Fragment, { children: gameId && /* @__PURE__ */ jsx(ConnectForGame, { gameId, ...props }) });
96
+ };
97
+ const ConnectForGame = ({ onComplete, onError, helpPage, gameId, ...boxProps }) => {
98
+ useGoogleStatusWatching({
99
+ projectId: gameId,
100
+ isWatching: true,
101
+ onGoogleStatusUpdate: (status) => {
102
+ if (status.isAuthenticated) return onComplete();
103
+ }
104
+ });
105
+ useInput(async (input) => {
106
+ if (!gameId) return;
107
+ if (input !== "d") return;
108
+ const url = await getConnectUrl(gameId, true);
109
+ await open(url);
110
+ });
111
+ const templateVars = {
112
+ privacyURL: new URL("/privacy", WEB_URL).toString()
113
+ };
114
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, ...boxProps, children: [
115
+ /* @__PURE__ */ jsx(Markdown, { filename: "privacy-notification.md", templateVars }),
116
+ /* @__PURE__ */ jsx(Text, { children: "Scan the QR code below to connect your Google account to ShipThis:" }),
117
+ gameId && /* @__PURE__ */ jsx(GoogleAuthQRCode, { gameId, helpPage: !!helpPage }),
118
+ /* @__PURE__ */ jsx(Text, { children: "Or press D to sign-in using your browser" })
119
+ ] });
120
+ };
121
+
122
+ export { ConnectGoogle as C };
@@ -0,0 +1,45 @@
1
+ import { u as useJob } from './Command-BiB9MqbJ.js';
2
+ import { useState, useEffect } from 'react';
3
+ import { u as useWebSocket } from './useWebSocket-CBqsjHbt.js';
4
+ import { E as castJobDates, F as castObjectDates } from './index-BB7X1Pqp.js';
5
+
6
+ function useJobWatching({ projectId, jobId, isWatching, onJobUpdate }) {
7
+ const [websocketJob, setWebsocketJob] = useState(null);
8
+ const [mostRecentLog, setMostRecentLog] = useState(null);
9
+ const jobStatusListener = {
10
+ getPattern: () => [`project.${projectId}:job:created`, `project.${projectId}:job:updated`],
11
+ eventHandler: async (pattern, rawJob) => {
12
+ if (rawJob.id !== jobId) return;
13
+ const job2 = castJobDates(rawJob);
14
+ setWebsocketJob(job2);
15
+ if (onJobUpdate) onJobUpdate(job2);
16
+ }
17
+ };
18
+ const jobProgressListener = {
19
+ getPattern: () => `project.${projectId}:job.${jobId}:log`,
20
+ eventHandler: async (pattern, rawLogEntry) => {
21
+ const logEntry = castObjectDates(rawLogEntry, ["sentAt", "createdAt"]);
22
+ setMostRecentLog(logEntry);
23
+ }
24
+ };
25
+ useWebSocket(isWatching ? [jobStatusListener, jobProgressListener] : []);
26
+ const { isLoading, data: job } = useJob({
27
+ projectId,
28
+ jobId
29
+ });
30
+ useEffect(() => {
31
+ setWebsocketJob(null);
32
+ }, [jobId, projectId, isWatching, job]);
33
+ const fetchedJob = job ? job : null;
34
+ const data = websocketJob ? websocketJob : fetchedJob;
35
+ const progress = mostRecentLog?.progress || null;
36
+ const stage = mostRecentLog?.stage || null;
37
+ return {
38
+ isLoading,
39
+ data,
40
+ progress,
41
+ stage
42
+ };
43
+ }
44
+
45
+ export { useJobWatching as u };
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "shipthis",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "shipthis",
9
- "version": "0.1.10",
9
+ "version": "0.1.11",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@expo/apple-utils": "2.0.3",
@@ -1827,14 +1827,15 @@
1827
1827
  "aliases": [],
1828
1828
  "args": {
1829
1829
  "file": {
1830
- "description": "Name of the ZIP file to import (must be in the same format as the export)",
1830
+ "description": "Path to the ZIP file to import (must be in the same format as the export)",
1831
1831
  "name": "file",
1832
- "required": true
1832
+ "required": false
1833
1833
  }
1834
1834
  },
1835
1835
  "description": "Imports an Android Keystore to your ShipThis account for the specified game.",
1836
1836
  "examples": [
1837
- "<%= config.bin %> <%= command.id %>"
1837
+ "<%= config.bin %> <%= command.id %> path/to/import.zip -g abfd5b00",
1838
+ "<%= config.bin %> <%= command.id %> --jksFile path/to/file.jks --keystorePassword yourpass --keyPassword yourkeypass"
1838
1839
  ],
1839
1840
  "flags": {
1840
1841
  "gameId": {
@@ -1845,8 +1846,30 @@
1845
1846
  "multiple": false,
1846
1847
  "type": "option"
1847
1848
  },
1849
+ "jksFile": {
1850
+ "description": "Path to the JKS file to import (requires passwords)",
1851
+ "name": "jksFile",
1852
+ "hasDynamicHelp": false,
1853
+ "multiple": false,
1854
+ "type": "option"
1855
+ },
1856
+ "keystorePassword": {
1857
+ "description": "Keystore password (required when using --jksFile)",
1858
+ "name": "keystorePassword",
1859
+ "hasDynamicHelp": false,
1860
+ "multiple": false,
1861
+ "type": "option"
1862
+ },
1863
+ "keyPassword": {
1864
+ "description": "Key alias password (required when using --jksFile)",
1865
+ "name": "keyPassword",
1866
+ "hasDynamicHelp": false,
1867
+ "multiple": false,
1868
+ "type": "option"
1869
+ },
1848
1870
  "force": {
1849
1871
  "char": "f",
1872
+ "description": "Overwrite any existing keystore without confirmation",
1850
1873
  "name": "force",
1851
1874
  "allowNo": false,
1852
1875
  "type": "boolean"
@@ -2297,5 +2320,5 @@
2297
2320
  ]
2298
2321
  }
2299
2322
  },
2300
- "version": "0.1.10"
2323
+ "version": "0.1.11"
2301
2324
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shipthis",
3
3
  "description": "ShipThis manages building and uploading your Godot games to the App Store and Google Play.",
4
- "version": "0.1.10",
4
+ "version": "0.1.11",
5
5
  "author": "Hello Invent Ltd",
6
6
  "bin": {
7
7
  "shipthis": "./bin/run.js"