shipthis 0.1.30 → 0.1.32

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 (86) hide show
  1. package/README.md +122 -41
  2. package/assets/markdown/create-google-play-game.md +2 -4
  3. package/assets/markdown/ship-success.md +1 -1
  4. package/dist/{AppleBundleIdDetails-Fp5COwTa.js → AppleBundleIdDetails-6H3cNWxw.js} +17 -19
  5. package/dist/{Command-1p5alCz3.js → Command-WPpmLPkL.js} +13 -12
  6. package/dist/CommandGame-cxzWG4nT.js +7 -0
  7. package/dist/{Create-1xAdntNl.js → Create-3Ob8sjik.js} +20 -20
  8. package/dist/GameStatus-BQEtVKvv.js +137 -0
  9. package/dist/{Import-CzC-M4ln.js → Import-CFuPDI0K.js} +33 -35
  10. package/dist/{JobLogTail-CZxoMSd5.js → JobLogTail-0CBLoG8N.js} +53 -52
  11. package/dist/{JobProgress-BjNgtIjm.js → JobProgress-lKqVT88m.js} +46 -37
  12. package/dist/{JobStatusTable-BB-PWlwj.js → JobStatusTable-C_ZsZJCm.js} +14 -13
  13. package/dist/{NextSteps-CK9zHOCt.js → NextSteps-DbJHmscQ.js} +1 -3
  14. package/dist/{ProgressSpinner-6pw1T8Iw.js → ProgressSpinner-DGcakQSK.js} +1 -1
  15. package/dist/{ProjectCredentialsTable-DyZep993.js → ProjectCredentialsTable-B5pHOnGu.js} +11 -10
  16. package/dist/{StatusTable-Dm5St4g-.js → StatusTable-DzRWcMr4.js} +7 -9
  17. package/dist/{Table-CvM6pccN.js → Table-FaNgpyeq.js} +15 -15
  18. package/dist/{UserCredentialsTable-BraKyDWT.js → UserCredentialsTable-3W3qesh7.js} +18 -19
  19. package/dist/{baseAppleCommand-BHRIBtTj.js → baseAppleCommand-BGV088--.js} +1 -1
  20. package/dist/{baseGameAndroidCommand-SrDRbhAG.js → baseGameAndroidCommand-CsemgVjp.js} +23 -23
  21. package/dist/commands/apple/apiKey/create.js +35 -35
  22. package/dist/commands/apple/apiKey/export.js +26 -26
  23. package/dist/commands/apple/apiKey/import.js +27 -27
  24. package/dist/commands/apple/apiKey/status.js +31 -31
  25. package/dist/commands/apple/certificate/create.js +39 -39
  26. package/dist/commands/apple/certificate/export.js +26 -26
  27. package/dist/commands/apple/certificate/import.js +27 -27
  28. package/dist/commands/apple/certificate/status.js +31 -31
  29. package/dist/commands/apple/login.js +15 -15
  30. package/dist/commands/apple/status.js +28 -28
  31. package/dist/commands/dashboard.js +10 -10
  32. package/dist/commands/game/android/apiKey/connect.js +28 -28
  33. package/dist/commands/game/android/apiKey/create.js +28 -28
  34. package/dist/commands/game/android/apiKey/export.js +29 -29
  35. package/dist/commands/game/android/apiKey/import.js +31 -31
  36. package/dist/commands/game/android/apiKey/invite.js +14 -14
  37. package/dist/commands/game/android/apiKey/status.js +29 -29
  38. package/dist/commands/game/android/keyStore/create.js +24 -24
  39. package/dist/commands/game/android/keyStore/export.js +28 -28
  40. package/dist/commands/game/android/keyStore/import.js +35 -35
  41. package/dist/commands/game/android/keyStore/status.js +26 -26
  42. package/dist/commands/game/android/status.js +14 -58
  43. package/dist/commands/game/build/download.js +24 -24
  44. package/dist/commands/game/build/list.js +37 -37
  45. package/dist/commands/game/create.js +15 -15
  46. package/dist/commands/game/details.js +35 -36
  47. package/dist/commands/game/export.js +12 -12
  48. package/dist/commands/game/ios/app/addTester.js +24 -24
  49. package/dist/commands/game/ios/app/create.js +24 -24
  50. package/dist/commands/game/ios/app/status.js +29 -29
  51. package/dist/commands/game/ios/app/sync.js +31 -31
  52. package/dist/commands/game/ios/profile/create.js +30 -30
  53. package/dist/commands/game/ios/profile/export.js +28 -28
  54. package/dist/commands/game/ios/profile/import.js +32 -32
  55. package/dist/commands/game/ios/profile/status.js +36 -36
  56. package/dist/commands/game/ios/status.js +46 -58
  57. package/dist/commands/game/ios/wizard.js +31 -31
  58. package/dist/commands/game/job/list.js +34 -34
  59. package/dist/commands/game/job/status.js +31 -31
  60. package/dist/commands/game/list.js +45 -41
  61. package/dist/commands/game/ship.js +73 -70
  62. package/dist/commands/game/status.js +38 -82
  63. package/dist/commands/game/wizard.js +271 -307
  64. package/dist/commands/internal/fastlane.js +15 -17
  65. package/dist/commands/internal/readme.js +38 -36
  66. package/dist/commands/login.js +14 -14
  67. package/dist/commands/status.js +35 -33
  68. package/dist/{export-BKn02-NH.js → export-CXsVPXA1.js} +5 -5
  69. package/dist/{git-DREGq-jc.js → git-BpsfNFZ_.js} +8 -8
  70. package/dist/{import-CRMaNBVF.js → import-DGvG5REx.js} +14 -14
  71. package/dist/{index-DxzXU9Hd.js → index-BhhiXbey.js} +244 -221
  72. package/dist/{index-OZi8bvu8.js → index-C03TV1_J.js} +54 -38
  73. package/dist/{index-BTAL7EB_.js → index-C66Dd8Xc.js} +80 -79
  74. package/dist/{index-35Eswf6F.js → index-CGBdOm1q.js} +43 -27
  75. package/dist/{index--EbYyBAZ.js → index-CS9Gwcb0.js} +41 -43
  76. package/dist/{index-u1aj1OQW.js → index-CtTI85m-.js} +6 -6
  77. package/dist/{upload-Bw0zrS4M.js → upload-8y5MQEm9.js} +22 -22
  78. package/dist/{useAndroidServiceAccountTestResult-CJLIEYmA.js → useAndroidServiceAccountTestResult-DZk5SMxI.js} +11 -13
  79. package/dist/{useAppleApp-cnb8gX0x.js → useAppleApp-DWYGURwU.js} +4 -4
  80. package/dist/{useAppleBundleId-B0Etav8g.js → useAppleBundleId-PsTJ2g1B.js} +6 -6
  81. package/dist/{useProjectCredentials-DX3e_PPc.js → useProjectCredentials-BEphqa18.js} +10 -12
  82. package/dist/{useWebSocket-BOCa8v6o.js → useWebSocket-5PYa2QER.js} +1 -1
  83. package/dist/utils/help.js +4 -4
  84. package/package.json +4 -2
  85. package/dist/CommandGame-Z4eUQBjn.js +0 -9
  86. package/dist/{RunWithSpinner-BVXNWGD3.js → RunWithSpinner-gMVA07bZ.js} +2 -2
@@ -1,53 +1,79 @@
1
- import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
1
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
2
  import { Args } from '@oclif/core';
3
- import { Text, Box, useInput, useFocus, measureElement, render } from 'ink';
4
- import { p as getAuthedHeaders, q as API_URL, I as castArrayObjectDates, K 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, W as WEB_URL, F as getProject, C as CredentialsType, R as getGoogleStatus, B as BaseAuthenticatedCommand, j as isCWDGodotGame } from '../../index-DxzXU9Hd.js';
5
- import React, { useState, useContext, useEffect, useRef, useReducer } from 'react';
6
- import { d as CommandContext, b as GameContext, M as Markdown, u as useBuilds, q as queryBuilds, G as GameProvider, f as CreateGooglePlayGame } from '../../index-BTAL7EB_.js';
3
+ import { useScreenSize, withFullScreen } from 'fullscreen-ink';
4
+ import { K as queryClient, p as getAuthedHeaders, o as API_URL, I as castArrayObjectDates, a9 as updateProject, t as getGodotVersion, s as GameEngine, u as createProject, v as DEFAULT_SHIPPED_FILES_GLOBS, w as DEFAULT_IGNORED_FILES_GLOBS, P as Platform, J as JobStatus, W as WEB_URL, E as getProject, C as CredentialsType, Q as getGoogleStatus, B as BaseAuthenticatedCommand, j as isCWDGodotGame } from '../../index-BhhiXbey.js';
5
+ import { Box, Text, useInput } from 'ink';
7
6
  import Spinner from 'ink-spinner';
8
- import { Alert, TextInput } from '@inkjs/ui';
9
- import axios from 'axios';
10
- import 'crypto-js';
11
- import 'uuid';
12
- import fs__default from 'fs';
13
- import 'luxon';
14
- import { useQuery, useMutation } from '@tanstack/react-query';
15
- import 'yazl';
16
- import 'crypto';
17
- import 'readline-sync';
18
- import 'node:readline';
7
+ import 'node:crypto';
8
+ import fs__default from 'node:fs';
19
9
  import 'node:path';
10
+ import 'node:readline';
20
11
  import 'node:url';
12
+ import 'readline-sync';
13
+ import 'luxon';
14
+ import axios from 'axios';
21
15
  import 'isomorphic-git';
16
+ import { useMutation, useQuery } from '@tanstack/react-query';
17
+ import React, { useState, useContext, useEffect, useRef } from 'react';
18
+ import 'crypto-js';
19
+ import 'uuid';
22
20
  import 'fast-glob';
21
+ import 'yazl';
23
22
  import 'socket.io-client';
23
+ import 'string-length';
24
+ import 'strip-ansi';
24
25
  import 'open';
26
+ import { C as ConnectGoogle } from '../../index-C03TV1_J.js';
27
+ import { C as CommandContext, G as GameContext, u as useBuilds, M as Markdown, q as queryBuilds, c as GameProvider, f as CreateGooglePlayGame } from '../../index-C66Dd8Xc.js';
28
+ import { TextInput, Alert } from '@inkjs/ui';
25
29
  import 'marked';
26
30
  import 'marked-terminal';
27
- import 'path';
28
31
  import 'qrcode';
29
- import 'string-length';
30
- import 'strip-ansi';
31
- import { C as CreateKeystore } from '../../Create-1xAdntNl.js';
32
- import { I as ImportKeystore } from '../../Import-CzC-M4ln.js';
33
- import { C as ConnectGoogle } from '../../index-OZi8bvu8.js';
34
- import { C as CreateServiceAccountKey } from '../../index--EbYyBAZ.js';
35
- import { g as getShortUUID } from '../../index-35Eswf6F.js';
36
- import { u as useShip, J as JobProgress } from '../../JobProgress-BjNgtIjm.js';
37
- import { c as cacheKeys, f as fetchKeyTestResult, K as KeyTestStatus, a as KeyTestError } from '../../useAndroidServiceAccountTestResult-CJLIEYmA.js';
38
- import { J as JobLogTail } from '../../JobLogTail-CZxoMSd5.js';
39
- import { a as getProjectCredentials } from '../../index-u1aj1OQW.js';
32
+ import { g as getShortUUID } from '../../index-CGBdOm1q.js';
33
+ import { J as JobLogTail } from '../../JobLogTail-0CBLoG8N.js';
34
+ import { c as cacheKeys, f as fetchKeyTestResult, K as KeyTestStatus, a as KeyTestError } from '../../useAndroidServiceAccountTestResult-DZk5SMxI.js';
35
+ import { u as useShip, J as JobProgress } from '../../JobProgress-lKqVT88m.js';
36
+ import { C as CreateServiceAccountKey } from '../../index-CS9Gwcb0.js';
37
+ import { C as CreateKeystore } from '../../Create-3Ob8sjik.js';
38
+ import { I as ImportKeystore } from '../../Import-CFuPDI0K.js';
39
+ import { a as getProjectCredentials } from '../../index-CtTI85m-.js';
40
40
  import { T as Title } from '../../Title-BCQtayg6.js';
41
- import { C as Command } from '../../Command-1p5alCz3.js';
41
+ import { C as Command } from '../../Command-WPpmLPkL.js';
42
42
  import '@expo/apple-utils/build/index.js';
43
- import 'ini';
44
43
  import 'deepmerge';
45
- import '../../RunWithSpinner-BVXNWGD3.js';
46
- import '../../import-CRMaNBVF.js';
47
- import '../../useWebSocket-BOCa8v6o.js';
48
- import '../../useProjectCredentials-DX3e_PPc.js';
49
- import '../../ProgressSpinner-6pw1T8Iw.js';
50
- import '../../git-DREGq-jc.js';
44
+ import 'ini';
45
+ import '../../useWebSocket-5PYa2QER.js';
46
+ import '../../git-BpsfNFZ_.js';
47
+ import '../../ProgressSpinner-DGcakQSK.js';
48
+ import '../../useProjectCredentials-BEphqa18.js';
49
+ import '../../RunWithSpinner-gMVA07bZ.js';
50
+ import '../../import-DGvG5REx.js';
51
+
52
+ const useInviteServiceAccount = () => useMutation({
53
+ async mutationFn({ developerId, projectId }) {
54
+ try {
55
+ const headers = getAuthedHeaders();
56
+ const { data } = await axios.post(
57
+ `${API_URL}/projects/${projectId}/credentials/android/key/invite/`,
58
+ { developerId },
59
+ {
60
+ headers
61
+ }
62
+ );
63
+ return data;
64
+ } catch (error) {
65
+ console.error("useInviteMutation Error", error);
66
+ throw error;
67
+ }
68
+ },
69
+ async onSuccess(data) {
70
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
71
+ await sleep(1e3);
72
+ queryClient.invalidateQueries({
73
+ queryKey: cacheKeys.androidKeyTestResult({ projectId: data.projectId })
74
+ });
75
+ }
76
+ });
51
77
 
52
78
  async function queryJobs({ projectId, ...pageAndSortParams }) {
53
79
  try {
@@ -65,39 +91,30 @@ async function queryJobs({ projectId, ...pageAndSortParams }) {
65
91
  }
66
92
  const useJobs = (props) => {
67
93
  const queryResult = useQuery({
68
- queryKey: cacheKeys.jobs(props),
69
- queryFn: async () => queryJobs(props)
94
+ queryFn: async () => queryJobs(props),
95
+ queryKey: cacheKeys.jobs(props)
70
96
  });
71
97
  return queryResult;
72
98
  };
73
99
 
74
- const useInviteServiceAccount = () => {
75
- return useMutation({
76
- mutationFn: async ({ projectId, developerId }) => {
77
- try {
78
- const headers = getAuthedHeaders();
79
- const { data } = await axios.post(
80
- `${API_URL}/projects/${projectId}/credentials/android/key/invite/`,
81
- { developerId },
82
- {
83
- headers
84
- }
85
- );
86
- return data;
87
- } catch (error) {
88
- console.error("useInviteMutation Error", error);
89
- throw error;
90
- }
91
- },
92
- onSuccess: async (data) => {
93
- const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
94
- await sleep(1e3);
95
- queryClient.invalidateQueries({
96
- queryKey: cacheKeys.androidKeyTestResult({ projectId: data.projectId })
97
- });
98
- }
99
- });
100
- };
100
+ const WIDE_BREAKPOINT = 100;
101
+ const TALL_BREAKPOINT = 35;
102
+ function useResponsive() {
103
+ const { height, width } = useScreenSize();
104
+ const isWide = width >= WIDE_BREAKPOINT;
105
+ const isTall = height >= TALL_BREAKPOINT;
106
+ return {
107
+ height,
108
+ isTall,
109
+ isWide,
110
+ width
111
+ };
112
+ }
113
+
114
+ const FormTextInput = ({ label, labelProps, ...rest }) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
115
+ /* @__PURE__ */ jsx(Text, { ...labelProps, children: label }),
116
+ /* @__PURE__ */ jsx(TextInput, { ...rest })
117
+ ] });
101
118
 
102
119
  const GameInfoForm = ({ gameInfo, onSubmit }) => {
103
120
  const [activeInput, setActiveInput] = useState("name");
@@ -114,18 +131,18 @@ const GameInfoForm = ({ gameInfo, onSubmit }) => {
114
131
  };
115
132
  const handleSubmitPackageName = () => {
116
133
  setError(null);
117
- const packageRegex = /^[a-zA-Z][a-zA-Z0-9_]*(\.[a-zA-Z][a-zA-Z0-9_]*)+$/;
134
+ const packageRegex = /^[A-Za-z]\w*(\.[A-Za-z]\w*)+$/;
118
135
  if (!packageRegex.test(`${androidPackageName}`)) {
119
136
  setError("Please enter a valid package name e.g. com.flappy.souls");
120
137
  return;
121
138
  }
122
139
  onSubmit({
123
140
  ...gameInfo,
124
- name,
125
141
  details: {
126
142
  ...gameInfo.details,
127
143
  androidPackageName
128
- }
144
+ },
145
+ name
129
146
  });
130
147
  };
131
148
  return /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -135,23 +152,23 @@ const GameInfoForm = ({ gameInfo, onSubmit }) => {
135
152
  /* @__PURE__ */ jsx(
136
153
  FormTextInput,
137
154
  {
138
- label: "Game name:",
139
- isDisabled: activeInput !== "name",
140
155
  defaultValue: name,
141
- placeholder: "Enter the name of your game...",
156
+ isDisabled: activeInput !== "name",
157
+ label: "Game name:",
142
158
  onChange: setName,
143
- onSubmit: handleSubmitName
159
+ onSubmit: handleSubmitName,
160
+ placeholder: "Enter the name of your game..."
144
161
  }
145
162
  ),
146
163
  /* @__PURE__ */ jsx(
147
164
  FormTextInput,
148
165
  {
149
- label: "Android package name :",
150
- isDisabled: activeInput !== "androidPackageName",
151
166
  defaultValue: androidPackageName,
152
- placeholder: "e.g. com.flappy.souls",
167
+ isDisabled: activeInput !== "androidPackageName",
168
+ label: "Android package name :",
153
169
  onChange: setAndroidPackageName,
154
- onSubmit: handleSubmitPackageName
170
+ onSubmit: handleSubmitPackageName,
171
+ placeholder: "e.g. com.flappy.souls"
155
172
  }
156
173
  )
157
174
  ] })
@@ -161,11 +178,11 @@ const GameInfoForm = ({ gameInfo, onSubmit }) => {
161
178
  const getGameInfo = (flagValues, project) => {
162
179
  const androidPackageName = flagValues.androidPackageName || project?.details?.androidPackageName || "";
163
180
  const gameInfo = {
164
- name: project?.name || flagValues.name || "",
165
181
  details: {
166
182
  ...project?.details,
167
183
  androidPackageName
168
- }
184
+ },
185
+ name: project?.name || flagValues.name || ""
169
186
  };
170
187
  return gameInfo;
171
188
  };
@@ -174,11 +191,11 @@ const CreateGame = (props) => {
174
191
  const [gameInfo, setGameInfo] = useState(null);
175
192
  const [showForm, setShowForm] = useState(false);
176
193
  const { command } = useContext(CommandContext);
177
- const { setGameId, game } = useContext(GameContext);
194
+ const { game, setGameId } = useContext(GameContext);
178
195
  const handleLoad = async () => {
179
196
  if (!command) throw new Error("No command");
180
197
  const flags = command.getDetailsFlagsValues();
181
- const config = await command.getProjectConfigSafe();
198
+ const config = command.getProjectConfigSafe();
182
199
  setShowForm(true);
183
200
  setIsLoading(false);
184
201
  const info = getGameInfo(flags, config.project);
@@ -192,7 +209,7 @@ const CreateGame = (props) => {
192
209
  setShowForm(false);
193
210
  setIsLoading(true);
194
211
  if (!command) throw new Error("No command");
195
- const projectConfig = await command.getProjectConfigSafe();
212
+ const projectConfig = command.getProjectConfigSafe();
196
213
  const existingGame = projectConfig.project;
197
214
  const isNew = !existingGame;
198
215
  if (!isNew) {
@@ -204,142 +221,40 @@ const CreateGame = (props) => {
204
221
  await command.setProjectConfig(updatedConfig);
205
222
  return props.onComplete();
206
223
  }
207
- const { name, details } = gameInfo2;
224
+ const { details, name } = gameInfo2;
208
225
  const projectDetails = {
209
226
  ...details,
210
227
  gameEngine: GameEngine.GODOT,
211
228
  gameEngineVersion: getGodotVersion()
212
229
  };
213
- const project = await createProject({ name, details: projectDetails });
230
+ const project = await createProject({ details: projectDetails, name });
214
231
  await command.setProjectConfig({
232
+ ignoredFilesGlobs: DEFAULT_IGNORED_FILES_GLOBS,
215
233
  project,
216
- shippedFilesGlobs: DEFAULT_SHIPPED_FILES_GLOBS,
217
- ignoredFilesGlobs: DEFAULT_IGNORED_FILES_GLOBS
234
+ shippedFilesGlobs: DEFAULT_SHIPPED_FILES_GLOBS
218
235
  });
219
236
  setGameId(project.id);
220
237
  props.onComplete();
221
- } catch (e) {
222
- props.onError(e);
238
+ } catch (error) {
239
+ props.onError(error);
223
240
  }
224
241
  };
225
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, borderStyle: "single", margin: 1, children: [
242
+ return /* @__PURE__ */ jsxs(Box, { borderStyle: "single", flexDirection: "column", gap: 1, margin: 1, children: [
226
243
  isLoading && /* @__PURE__ */ jsx(Spinner, {}),
227
244
  showForm && gameInfo && /* @__PURE__ */ jsx(GameInfoForm, { gameInfo, onSubmit: handleSubmitForm })
228
245
  ] });
229
246
  };
230
247
 
231
- const FormTextInput = ({ label, labelProps, ...rest }) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
232
- /* @__PURE__ */ jsx(Text, { ...labelProps, children: label }),
233
- /* @__PURE__ */ jsx(TextInput, { ...rest })
234
- ] });
235
-
236
- const ImportForm = ({ importKeystoreProps, onSubmit }) => {
237
- const [activeInput, setActiveInput] = useState("jksFilePath" /* jksFilePath */);
238
- const [error, setError] = useState(null);
239
- const [jksFilePath, setJksFilePath] = useState(importKeystoreProps.jksFilePath);
240
- const [password, setPassword] = useState(importKeystoreProps.keyPassword);
241
- const handleSubmitJksFilePath = () => {
242
- setError(null);
243
- if (!jksFilePath || jksFilePath.length === 0) {
244
- setError("Please enter a path to your jks file");
245
- return;
246
- }
247
- if (!fs__default.existsSync(jksFilePath)) {
248
- setError("The file does not exist");
249
- return;
250
- }
251
- setActiveInput("password" /* password */);
252
- };
253
- const handleSubmitPassword = () => {
254
- setError(null);
255
- if (!password || password.length === 0) {
256
- setError("Please enter a password");
257
- return;
258
- }
259
- onSubmit({
260
- ...importKeystoreProps,
261
- jksFilePath,
262
- keyPassword: password,
263
- keystorePassword: password
264
- });
265
- };
266
- return /* @__PURE__ */ jsxs(Fragment, { children: [
267
- error && /* @__PURE__ */ jsx(Alert, { variant: "error", children: error }),
268
- /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [
269
- /* @__PURE__ */ jsx(
270
- FormTextInput,
271
- {
272
- label: "Path to your jks file:",
273
- isDisabled: activeInput !== "jksFilePath" /* jksFilePath */,
274
- defaultValue: jksFilePath,
275
- placeholder: "Enter the path to your jks file...",
276
- onChange: setJksFilePath,
277
- onSubmit: handleSubmitJksFilePath
278
- }
279
- ),
280
- /* @__PURE__ */ jsx(
281
- FormTextInput,
282
- {
283
- label: "Password:",
284
- isDisabled: activeInput !== "password" /* password */,
285
- defaultValue: password,
286
- placeholder: "Enter the password for your jks file...",
287
- onChange: setPassword,
288
- onSubmit: handleSubmitPassword
289
- }
290
- )
291
- ] })
292
- ] });
293
- };
294
-
295
- const CreateOrImport = ({ onComplete, onError, ...boxProps }) => {
296
- const [stage, setStage] = useState(0 /* Choose */);
297
- const [importKeystoreProps, setImportKeystoreProps] = useState({
298
- jksFilePath: "",
299
- keyPassword: "",
300
- keystorePassword: ""
301
- });
302
- useInput(async (input) => {
303
- if (stage !== 0 /* Choose */) return;
304
- if (input === "c") return setStage(1 /* Create */);
305
- if (input === "i") return setStage(2 /* ImportForm */);
306
- });
307
- const handleImportFormSubmit = (newImportProps) => {
308
- setImportKeystoreProps(newImportProps);
309
- setStage(3 /* ImportKeystore */);
310
- };
311
- const renderStage = () => {
312
- switch (stage) {
313
- case 0 /* Choose */:
314
- return /* @__PURE__ */ jsxs(Fragment, { children: [
315
- /* @__PURE__ */ jsx(Text, { children: "Would you like to create a new keystore or import an existing one?" }),
316
- /* @__PURE__ */ jsx(Text, { bold: true, children: "Press C to create a new keystore" }),
317
- /* @__PURE__ */ jsx(Text, { bold: true, children: "Press I to import an existing keystore" })
318
- ] });
319
- case 1 /* Create */:
320
- return /* @__PURE__ */ jsx(CreateKeystore, { onComplete, onError });
321
- case 2 /* ImportForm */:
322
- return /* @__PURE__ */ jsx(ImportForm, { onSubmit: handleImportFormSubmit, importKeystoreProps });
323
- case 3 /* ImportKeystore */:
324
- return /* @__PURE__ */ jsx(ImportKeystore, { onComplete, onError, importKeystoreProps });
325
- }
326
- };
327
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, ...boxProps, children: [
328
- /* @__PURE__ */ jsx(Markdown, { filename: "create-or-import-keystore.md", templateVars: {} }),
329
- renderStage()
330
- ] });
331
- };
332
-
333
248
  const CreateInitialBuild = (props) => {
334
249
  const { gameId } = useContext(GameContext);
335
250
  return /* @__PURE__ */ jsx(Fragment, { children: gameId && /* @__PURE__ */ jsx(CreateForGame, { gameId, ...props }) });
336
251
  };
337
- const CreateForGame = ({ onComplete, onError, gameId, ...boxProps }) => {
252
+ const CreateForGame = ({ gameId, onComplete, onError, ...boxProps }) => {
338
253
  const { command } = useContext(CommandContext);
339
- const { data: buildData, isLoading: isLoadingBuilds } = useBuilds({ projectId: gameId, pageNumber: 0 });
254
+ const { data: buildData, isLoading: isLoadingBuilds } = useBuilds({ pageNumber: 0, projectId: gameId });
340
255
  const { data: jobData, isLoading: isLoadingJobs } = useJobs({
341
- projectId: gameId,
342
- pageNumber: 0
256
+ pageNumber: 0,
257
+ projectId: gameId
343
258
  });
344
259
  const prevHasBuild = useRef(false);
345
260
  const shipMutation = useShip();
@@ -360,7 +275,11 @@ const CreateForGame = ({ onComplete, onError, gameId, ...boxProps }) => {
360
275
  if (shouldRun)
361
276
  shipMutation.mutateAsync({
362
277
  command,
363
- log: setShipLog
278
+ log: setShipLog,
279
+ shipFlags: {
280
+ platform: "android",
281
+ skipPublish: true
282
+ }
364
283
  }).catch(onError);
365
284
  }, [buildData, jobData, command]);
366
285
  const androidJob = jobData?.data.find(
@@ -388,7 +307,7 @@ const CreateForGame = ({ onComplete, onError, gameId, ...boxProps }) => {
388
307
  }
389
308
  }
390
309
  ),
391
- /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(JobLogTail, { jobId: failedJob.id, projectId: gameId, isWatching: false, length: 10 }) })
310
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(JobLogTail, { isWatching: false, jobId: failedJob.id, length: 10, projectId: gameId }) })
392
311
  ] })
393
312
  ] }) });
394
313
  };
@@ -409,12 +328,12 @@ const InviteForm = ({ onSubmit }) => {
409
328
  /* @__PURE__ */ jsx(
410
329
  FormTextInput,
411
330
  {
331
+ defaultValue: accountId,
412
332
  label: "Please enter your Google Play Account ID:",
413
333
  labelProps: { bold: true },
414
- defaultValue: accountId,
415
- placeholder: "e.g. 8110853839480950872",
416
334
  onChange: setAccountId,
417
- onSubmit: handleSubmitAccountId
335
+ onSubmit: handleSubmitAccountId,
336
+ placeholder: "e.g. 8110853839480950872"
418
337
  }
419
338
  ),
420
339
  error && /* @__PURE__ */ jsx(Alert, { variant: "error", children: error })
@@ -427,7 +346,7 @@ const InviteServiceAccount = ({ onComplete, onError, ...boxProps }) => {
427
346
  const handleSubmit = async (developerId) => {
428
347
  try {
429
348
  if (!gameId) return;
430
- await inviteMutation.mutateAsync({ projectId: gameId, developerId });
349
+ await inviteMutation.mutateAsync({ developerId, projectId: gameId });
431
350
  onComplete();
432
351
  } catch (error) {
433
352
  onError(error);
@@ -445,11 +364,112 @@ const InviteServiceAccount = ({ onComplete, onError, ...boxProps }) => {
445
364
  ] }) });
446
365
  };
447
366
 
367
+ const ImportForm = ({ importKeystoreProps, onSubmit }) => {
368
+ const [activeInput, setActiveInput] = useState("jksFilePath" /* jksFilePath */);
369
+ const [error, setError] = useState(null);
370
+ const [jksFilePath, setJksFilePath] = useState(importKeystoreProps.jksFilePath);
371
+ const [password, setPassword] = useState(importKeystoreProps.keyPassword);
372
+ const handleSubmitJksFilePath = () => {
373
+ setError(null);
374
+ if (!jksFilePath || jksFilePath.length === 0) {
375
+ setError("Please enter a path to your jks file");
376
+ return;
377
+ }
378
+ if (!fs__default.existsSync(jksFilePath)) {
379
+ setError("The file does not exist");
380
+ return;
381
+ }
382
+ setActiveInput("password" /* password */);
383
+ };
384
+ const handleSubmitPassword = () => {
385
+ setError(null);
386
+ if (!password || password.length === 0) {
387
+ setError("Please enter a password");
388
+ return;
389
+ }
390
+ onSubmit({
391
+ ...importKeystoreProps,
392
+ jksFilePath,
393
+ keyPassword: password,
394
+ keystorePassword: password
395
+ });
396
+ };
397
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
398
+ error && /* @__PURE__ */ jsx(Alert, { variant: "error", children: error }),
399
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [
400
+ /* @__PURE__ */ jsx(
401
+ FormTextInput,
402
+ {
403
+ defaultValue: jksFilePath,
404
+ isDisabled: activeInput !== "jksFilePath" /* jksFilePath */,
405
+ label: "Path to your jks file:",
406
+ onChange: setJksFilePath,
407
+ onSubmit: handleSubmitJksFilePath,
408
+ placeholder: "Enter the path to your jks file..."
409
+ }
410
+ ),
411
+ /* @__PURE__ */ jsx(
412
+ FormTextInput,
413
+ {
414
+ defaultValue: password,
415
+ isDisabled: activeInput !== "password" /* password */,
416
+ label: "Password:",
417
+ onChange: setPassword,
418
+ onSubmit: handleSubmitPassword,
419
+ placeholder: "Enter the password for your jks file..."
420
+ }
421
+ )
422
+ ] })
423
+ ] });
424
+ };
425
+
426
+ const CreateOrImport = ({ onComplete, onError, ...boxProps }) => {
427
+ const [stage, setStage] = useState(0 /* Choose */);
428
+ const [importKeystoreProps, setImportKeystoreProps] = useState({
429
+ jksFilePath: "",
430
+ keyPassword: "",
431
+ keystorePassword: ""
432
+ });
433
+ useInput(async (input) => {
434
+ if (stage !== 0 /* Choose */) return;
435
+ if (input === "c") return setStage(1 /* Create */);
436
+ if (input === "i") return setStage(2 /* ImportForm */);
437
+ });
438
+ const handleImportFormSubmit = (newImportProps) => {
439
+ setImportKeystoreProps(newImportProps);
440
+ setStage(3 /* ImportKeystore */);
441
+ };
442
+ const renderStage = () => {
443
+ switch (stage) {
444
+ case 0 /* Choose */: {
445
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
446
+ /* @__PURE__ */ jsx(Text, { children: "Would you like to create a new keystore or import an existing one?" }),
447
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Press C to create a new keystore" }),
448
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Press I to import an existing keystore" })
449
+ ] });
450
+ }
451
+ case 1 /* Create */: {
452
+ return /* @__PURE__ */ jsx(CreateKeystore, { onComplete, onError });
453
+ }
454
+ case 2 /* ImportForm */: {
455
+ return /* @__PURE__ */ jsx(ImportForm, { importKeystoreProps, onSubmit: handleImportFormSubmit });
456
+ }
457
+ case 3 /* ImportKeystore */: {
458
+ return /* @__PURE__ */ jsx(ImportKeystore, { importKeystoreProps, onComplete, onError });
459
+ }
460
+ }
461
+ };
462
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, ...boxProps, children: [
463
+ /* @__PURE__ */ jsx(Markdown, { filename: "create-or-import-keystore.md", templateVars: {} }),
464
+ renderStage()
465
+ ] });
466
+ };
467
+
448
468
  var StepStatus = /* @__PURE__ */ ((StepStatus2) => {
469
+ StepStatus2["FAILURE"] = "FAILURE";
449
470
  StepStatus2["PENDING"] = "PENDING";
450
471
  StepStatus2["RUNNING"] = "RUNNING";
451
472
  StepStatus2["SUCCESS"] = "SUCCESS";
452
- StepStatus2["FAILURE"] = "FAILURE";
453
473
  StepStatus2["WARN"] = "WARN";
454
474
  return StepStatus2;
455
475
  })(StepStatus || {});
@@ -474,20 +494,20 @@ const getStepInitialStatus = (step, statusFlags) => {
474
494
  }
475
495
  const base = {
476
496
  createGame: statusFlags.hasGameName && statusFlags.hasAndroidPackageName,
497
+ createGooglePlayGame: statusFlags.hasGooglePlayGame,
477
498
  createKeystore: statusFlags.hasAndroidKeystore,
478
499
  createServiceAccount: statusFlags.hasServiceAccountKey,
479
- createGooglePlayGame: statusFlags.hasGooglePlayGame,
480
500
  inviteServiceAccount: statusFlags.hasInvitedServiceAccount
481
501
  };
482
502
  return base[step] ? "SUCCESS" /* SUCCESS */ : "PENDING" /* PENDING */;
483
503
  };
484
504
  const getStatusFlags = async (cmd) => {
485
- const projectConfig = await cmd.getProjectConfigSafe();
505
+ const projectConfig = cmd.getProjectConfigSafe();
486
506
  const projectId = projectConfig.project?.id;
487
- const project = !!projectId && await getProject(projectId);
488
- const hasShipThisProject = !!project;
489
- const hasGameName = project && !!project?.name;
490
- const hasAndroidPackageName = project && !!project?.details?.androidPackageName;
507
+ const project = Boolean(projectId) && await getProject(projectId);
508
+ const hasShipThisProject = Boolean(project);
509
+ const hasGameName = project && Boolean(project?.name);
510
+ const hasAndroidPackageName = project && Boolean(project?.details?.androidPackageName);
491
511
  const projectCredentials = hasShipThisProject ? await getProjectCredentials(project.id) : [];
492
512
  const hasAndroidKeystore = projectCredentials.some(
493
513
  (cred) => cred.isActive && cred.platform === Platform.ANDROID && cred.type == CredentialsType.CERTIFICATE
@@ -497,35 +517,37 @@ const getStatusFlags = async (cmd) => {
497
517
  const hasServiceAccountKey = projectCredentials.some(
498
518
  (cred) => cred.isActive && cred.platform == Platform.ANDROID && cred.type == CredentialsType.KEY
499
519
  );
500
- const buildsResponse = !!projectId && hasShipThisProject && await queryBuilds({ projectId, pageNumber: 0 });
501
- const hasInitialBuild = !!buildsResponse && buildsResponse.data.some((build) => build.platform === Platform.ANDROID);
520
+ const buildsResponse = Boolean(projectId) && hasShipThisProject && await queryBuilds({ pageNumber: 0, projectId });
521
+ const hasInitialBuild = Boolean(buildsResponse) && buildsResponse.data.some((build) => build.platform === Platform.ANDROID);
502
522
  const testResult = projectId ? await fetchKeyTestResult({ projectId }) : null;
503
523
  const hasGooglePlayGame = testResult && testResult?.status === KeyTestStatus.SUCCESS || testResult?.status === KeyTestStatus.ERROR && testResult?.error === KeyTestError.NOT_INVITED;
504
524
  const hasInvitedServiceAccount = testResult ? testResult?.status === KeyTestStatus.SUCCESS : false;
505
525
  return {
506
- hasShipThisProject,
507
- hasGameName,
508
- hasAndroidPackageName,
509
526
  hasAndroidKeystore,
527
+ hasAndroidPackageName,
528
+ hasGameName,
510
529
  hasGoogleConnection,
511
- hasServiceAccountKey,
512
- hasInitialBuild,
513
530
  hasGooglePlayGame,
514
- hasInvitedServiceAccount
531
+ hasInitialBuild,
532
+ hasInvitedServiceAccount,
533
+ hasServiceAccountKey,
534
+ hasShipThisProject
515
535
  };
516
536
  };
517
537
 
518
538
  const StepLabels = {
539
+ connectGoogle: "Connect ShipThis with Google",
519
540
  createGame: "Create game in ShipThis",
541
+ createGooglePlayGame: "Create the game in Google Play",
542
+ createInitialBuild: "Create an initial build",
520
543
  createKeystore: "Create or import an Android Keystore",
521
- connectGoogle: "Connect ShipThis with Google",
522
544
  createServiceAccount: "Create a Service Account & API Key",
523
- createInitialBuild: "Create an initial build",
524
- createGooglePlayGame: "Create the game in Google Play",
525
545
  inviteServiceAccount: "Invite the Service Account"
526
546
  };
527
- const StepWithStatus = ({ position, title, status }) => {
547
+ const StepWithStatus = ({ position, status, title }) => {
528
548
  const indicator = {
549
+ [StepStatus.FAILURE]: "\u274C",
550
+ // this is 2 wide?
529
551
  [StepStatus.PENDING]: " ",
530
552
  // double space
531
553
  [StepStatus.RUNNING]: /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -534,8 +556,6 @@ const StepWithStatus = ({ position, title, status }) => {
534
556
  ] }),
535
557
  [StepStatus.SUCCESS]: "\u2705",
536
558
  // this is 2 wide?
537
- [StepStatus.FAILURE]: "\u274C",
538
- // this is 2 wide?
539
559
  [StepStatus.WARN]: "\u26A0\uFE0F "
540
560
  // double
541
561
  }[status];
@@ -548,37 +568,47 @@ const StepWithStatus = ({ position, title, status }) => {
548
568
  title
549
569
  ] });
550
570
  };
551
- const StepStatusTable = ({ stepStatuses }) => {
552
- return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginLeft: 1, children: Steps.map((step, index) => {
553
- return /* @__PURE__ */ jsx(StepWithStatus, { position: index + 1, title: StepLabels[step], status: stepStatuses[index] }, step);
554
- }) });
571
+ const StepStatusTable = ({ stepStatuses }) => /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginLeft: 1, children: Steps.map((step, index) => /* @__PURE__ */ jsx(StepWithStatus, { position: index + 1, status: stepStatuses[index], title: StepLabels[step] }, step)) });
572
+
573
+ const WizardHeader = ({ currentStepIndex, stepStatuses }) => {
574
+ const { isTall } = useResponsive();
575
+ const stepCount = stepStatuses ? stepStatuses.length : 0;
576
+ const currentStep = currentStepIndex === null ? null : currentStepIndex + 1;
577
+ const title = isTall ? "ShipThis Android Wizard" : `ShipThis Android Wizard (step ${currentStep} of ${stepCount})`;
578
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
579
+ /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Title, { children: title }) }),
580
+ stepStatuses && isTall && /* @__PURE__ */ jsx(StepStatusTable, { stepStatuses })
581
+ ] });
555
582
  };
556
583
 
557
584
  const stepComponentMap = {
585
+ connectGoogle: ConnectGoogle,
558
586
  createGame: CreateGame,
587
+ createGooglePlayGame: CreateGooglePlayGame,
588
+ createInitialBuild: CreateInitialBuild,
559
589
  createKeystore: CreateOrImport,
560
- connectGoogle: ConnectGoogle,
561
590
  createServiceAccount: CreateServiceAccountKey,
562
- createInitialBuild: CreateInitialBuild,
563
- createGooglePlayGame: CreateGooglePlayGame,
564
591
  inviteServiceAccount: InviteServiceAccount
565
592
  };
566
593
  const ON_COMPLETE_DELAY_MS = 500;
567
594
  const AndroidWizard = (props) => {
568
595
  const { command } = React.useContext(CommandContext);
596
+ const { isTall, isWide } = useResponsive();
569
597
  const [currentStep, setCurrentStep] = useState(null);
598
+ const [currentStepIndex, setCurrentStepIndex] = useState(null);
570
599
  const [stepStatuses, setStepStatuses] = useState(null);
571
600
  const [showSuccess, setShowSuccess] = useState(false);
572
601
  const determineStep = async () => {
573
602
  if (!command) return;
574
603
  const statusFlags = await getStatusFlags(command);
575
604
  const initStatuses = Steps.map((step) => getStepInitialStatus(step, statusFlags));
576
- const firstPending = initStatuses.findIndex((status) => status === StepStatus.PENDING);
605
+ const firstPending = initStatuses.indexOf(StepStatus.PENDING);
577
606
  const pendingStep = firstPending === -1 ? null : Steps[firstPending];
578
607
  const withPending = initStatuses.map((status, index) => {
579
608
  if (index === firstPending) return StepStatus.RUNNING;
580
609
  return status;
581
610
  });
611
+ setCurrentStepIndex(firstPending);
582
612
  setCurrentStep(pendingStep);
583
613
  setStepStatuses(withPending);
584
614
  const isAllDone = firstPending === -1;
@@ -591,97 +621,31 @@ const AndroidWizard = (props) => {
591
621
  const handleStepComplete = () => determineStep().catch(props.onError);
592
622
  const StepInterface = currentStep ? stepComponentMap[currentStep] : null;
593
623
  const templateVars = {
594
- iosSetupURL: new URL("/docs/ios", WEB_URL).toString(),
595
- docsURL: new URL("/docs", WEB_URL).toString()
624
+ docsURL: new URL("/docs", WEB_URL).toString(),
625
+ iosSetupURL: new URL("/docs/ios", WEB_URL).toString()
596
626
  };
597
- return /* @__PURE__ */ jsx(GameProvider, { children: /* @__PURE__ */ jsxs(ScrollArea, { height: process.stdout.rows || 24, children: [
598
- /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
599
- /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Title, { children: "ShipThis Android Wizard" }) }),
600
- stepStatuses && /* @__PURE__ */ jsx(StepStatusTable, { stepStatuses })
601
- ] }),
627
+ return /* @__PURE__ */ jsxs(GameProvider, { children: [
628
+ /* @__PURE__ */ jsx(WizardHeader, { currentStepIndex, stepStatuses }),
602
629
  StepInterface && /* @__PURE__ */ jsx(
603
630
  StepInterface,
604
631
  {
632
+ borderStyle: isTall && isWide ? "single" : void 0,
633
+ margin: isTall && isWide ? 1 : 0,
605
634
  onComplete: handleStepComplete,
606
635
  onError: props.onError,
607
- margin: 1,
608
- borderStyle: "single",
609
- padding: 1
636
+ padding: isTall && isWide ? 1 : 0
610
637
  }
611
638
  ),
612
639
  showSuccess && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Markdown, { filename: "android-success.md", templateVars }) })
613
- ] }) });
614
- };
615
-
616
- const reducer = (state, action) => {
617
- switch (action.type) {
618
- case "SET_INNER_HEIGHT":
619
- return {
620
- ...state,
621
- innerHeight: action.innerHeight
622
- };
623
- case "SET_HEIGHT":
624
- return {
625
- ...state,
626
- height: action.height
627
- };
628
- case "SCROLL_DOWN":
629
- return {
630
- ...state,
631
- scrollTop: Math.min(
632
- state.innerHeight <= state.height ? 0 : state.innerHeight - state.height,
633
- state.scrollTop + 1
634
- )
635
- };
636
- case "SCROLL_UP":
637
- return {
638
- ...state,
639
- scrollTop: Math.max(0, state.scrollTop - 1)
640
- };
641
- default:
642
- return state;
643
- }
640
+ ] });
644
641
  };
645
- function ScrollArea({ height, children }) {
646
- useFocus();
647
- const [state, dispatch] = useReducer(reducer, {
648
- height,
649
- scrollTop: 0,
650
- innerHeight: 0
651
- });
652
- const innerRef = useRef(null);
653
- useEffect(() => {
654
- dispatch({ type: "SET_HEIGHT", height });
655
- }, [height]);
656
- useEffect(() => {
657
- if (!innerRef.current) return;
658
- const dimensions = measureElement(innerRef.current);
659
- dispatch({
660
- type: "SET_INNER_HEIGHT",
661
- innerHeight: dimensions.height
662
- });
663
- }, []);
664
- useInput((_input, key) => {
665
- if (key.downArrow) {
666
- dispatch({
667
- type: "SCROLL_DOWN"
668
- });
669
- }
670
- if (key.upArrow) {
671
- dispatch({
672
- type: "SCROLL_UP"
673
- });
674
- }
675
- });
676
- return /* @__PURE__ */ jsx(Box, { height, flexDirection: "column", flexGrow: 1, overflow: "hidden", children: /* @__PURE__ */ jsx(Box, { ref: innerRef, flexShrink: 0, flexDirection: "column", marginTop: -state.scrollTop, children }) });
677
- }
678
642
 
679
643
  class GameWizard extends BaseAuthenticatedCommand {
680
644
  static args = {
681
645
  platform: Args.string({
682
646
  description: 'The platform to run the wizard for. This can be "android" or "ios"',
683
- required: true,
684
- options: ["android", "ios"]
647
+ options: ["android", "ios"],
648
+ required: true
685
649
  })
686
650
  };
687
651
  static description = "Runs all the steps for the specific platform";
@@ -695,9 +659,9 @@ class GameWizard extends BaseAuthenticatedCommand {
695
659
  if (args.platform === "ios") {
696
660
  return this.config.runCommand("game:ios:wizard");
697
661
  }
698
- render(
662
+ withFullScreen(
699
663
  /* @__PURE__ */ jsx(Command, { command: this, children: /* @__PURE__ */ jsx(AndroidWizard, { onComplete: () => process.exit(0), onError: (e) => this.error(e) }) })
700
- );
664
+ ).start();
701
665
  }
702
666
  }
703
667