@todesktop/cli 1.5.1 → 1.6.0

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.
package/dist/cli.js ADDED
@@ -0,0 +1,4409 @@
1
+ #!/usr/bin/env node
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
18
+ mod
19
+ ));
20
+
21
+ // src/index.ts
22
+ var import_commander = require("commander");
23
+ var import_ink32 = require("ink");
24
+ var import_react21 = require("react");
25
+ var import_register = require("source-map-support/register");
26
+
27
+ // src/components/Build.tsx
28
+ var import_react6 = require("react");
29
+ var import_prop_types9 = __toESM(require("prop-types"));
30
+
31
+ // src/components/BuildProgress.tsx
32
+ var import_ink4 = require("ink");
33
+ var import_prop_types3 = __toESM(require("prop-types"));
34
+ var import_react2 = require("react");
35
+ var import_lodash = __toESM(require("lodash.throttle"));
36
+
37
+ // src/components/BuildCompleteMessage.tsx
38
+ var import_ink2 = require("ink");
39
+ var import_ink_link = __toESM(require("ink-link"));
40
+ var import_prop_types = __toESM(require("prop-types"));
41
+ var import_react = require("react");
42
+
43
+ // src/utilities/logForCI.ts
44
+ var import_is_ci = __toESM(require("is-ci"));
45
+
46
+ // src/utilities/logger.ts
47
+ var import_bunyan = __toESM(require("bunyan"));
48
+ var fs = __toESM(require("fs"));
49
+ var os = __toESM(require("os"));
50
+ var path = __toESM(require("path"));
51
+ var Sentry = __toESM(require("@sentry/node"));
52
+ var logger;
53
+ try {
54
+ const name = "todesktop-cli";
55
+ let parentDirectory;
56
+ const homePath = os.homedir();
57
+ if (process.platform === "darwin") {
58
+ parentDirectory = path.join(homePath, "Library", "Logs", name);
59
+ } else if (process.platform === "win32") {
60
+ parentDirectory = path.join(
61
+ process.env.APPDATA || path.join(homePath, "AppData", "Roaming"),
62
+ name,
63
+ "logs"
64
+ );
65
+ } else {
66
+ parentDirectory = path.join(
67
+ process.env.XDG_CONFIG_HOME || path.join(homePath, ".config"),
68
+ name,
69
+ "logs"
70
+ );
71
+ }
72
+ fs.mkdirSync(parentDirectory, { recursive: true });
73
+ logger = import_bunyan.default.createLogger({
74
+ name,
75
+ src: true,
76
+ streams: [
77
+ {
78
+ level: "debug",
79
+ path: path.join(parentDirectory, "main.log")
80
+ }
81
+ ]
82
+ });
83
+ } catch (e) {
84
+ const noOp = () => {
85
+ };
86
+ logger = {
87
+ debug: noOp,
88
+ error: noOp,
89
+ fatal: noOp,
90
+ info: noOp,
91
+ trace: noOp,
92
+ warn: noOp
93
+ };
94
+ try {
95
+ Sentry.captureException(e);
96
+ } catch (err) {
97
+ }
98
+ }
99
+ var logger_default = logger;
100
+
101
+ // src/utilities/logForCI.ts
102
+ var logForCI_default = (...args2) => {
103
+ logger_default.info({ args: args2 }, "logForCI");
104
+ if (import_is_ci.default || !process.stdin.isTTY) {
105
+ console.log(...args2);
106
+ }
107
+ };
108
+
109
+ // src/utilities/useExit.ts
110
+ var import_ink = require("ink");
111
+
112
+ // src/utilities/analytics.ts
113
+ var import_os = __toESM(require("os"));
114
+ var import_uuid = require("uuid");
115
+ var import_analytics_node = __toESM(require("analytics-node"));
116
+
117
+ // src/utilities/getEnvironmentVariables.ts
118
+ var import_dotenv = __toESM(require("dotenv"));
119
+ var import_path = __toESM(require("path"));
120
+ var hasInitialized = false;
121
+ var init = () => {
122
+ if (hasInitialized) {
123
+ return;
124
+ }
125
+ import_dotenv.default.config({
126
+ path: import_path.default.resolve(__dirname, "../.env")
127
+ });
128
+ hasInitialized = true;
129
+ };
130
+ var getEnvironmentVariables_default = () => {
131
+ init();
132
+ return process.env;
133
+ };
134
+
135
+ // src/utilities/firestore.ts
136
+ var import_app = __toESM(require("firebase/app"));
137
+ var import_firestore = require("firebase/firestore");
138
+ var import_auth = require("firebase/auth");
139
+ var environmentVariables = getEnvironmentVariables_default();
140
+ var firebaseDB = import_app.default.initializeApp({
141
+ apiKey: environmentVariables.TODESKTOP_CLI_FIREBASE_API_KEY,
142
+ authDomain: environmentVariables.TODESKTOP_CLI_FIREBASE_AUTH_DOMAIN,
143
+ databaseURL: environmentVariables.TODESKTOP_CLI_FIREBASE_DATABASE_URL,
144
+ projectId: environmentVariables.TODESKTOP_CLI_FIREBASE_PROJECT_ID,
145
+ storageBucket: environmentVariables.TODESKTOP_CLI_FIREBASE_STORAGE_BUCKET,
146
+ messagingSenderId: environmentVariables.TODESKTOP_CLI_FIREBASE_MESSAGING_SENDER_ID,
147
+ appId: environmentVariables.TODESKTOP_CLI_FIREBASE_ID
148
+ });
149
+ var currentUser = () => import_app.default.auth().currentUser;
150
+ var signInWithCustomToken = async (token) => {
151
+ return import_app.default.auth().signInWithCustomToken(token);
152
+ };
153
+ var onUserAuth = (handler) => import_app.default.auth().onAuthStateChanged((user) => {
154
+ handler(user || {});
155
+ });
156
+ var firestore_default = firebaseDB.firestore();
157
+
158
+ // src/utilities/configStore.ts
159
+ var fs2 = __toESM(require("fs"));
160
+ var import_del = __toESM(require("del"));
161
+ var import_conf = __toESM(require("conf"));
162
+ var import_xdg_basedir = __toESM(require("xdg-basedir"));
163
+ var config = new import_conf.default({ configName: "todesktop-cli" });
164
+ var accessTokenConfigKey = "accessToken";
165
+ var emailConfigKey = "email";
166
+ var jwtTokenConfigKey = "jwtToken";
167
+ var setConfig = (key, value) => config.set(key, value);
168
+ var getConfig = (key) => config.get(key);
169
+ var setAuthConfig = (email, accessToken, jwtToken) => {
170
+ setConfig(emailConfigKey, email);
171
+ setConfig(accessTokenConfigKey, accessToken);
172
+ setConfig(jwtTokenConfigKey, jwtToken);
173
+ };
174
+ var getAuthConfig = () => {
175
+ const accessToken = getConfig(accessTokenConfigKey);
176
+ const jwtToken = getConfig(jwtTokenConfigKey);
177
+ const email = getConfig(emailConfigKey);
178
+ return { accessToken, jwtToken, email };
179
+ };
180
+ var deleteAuthConfig = () => {
181
+ config.delete(emailConfigKey);
182
+ config.delete(accessTokenConfigKey);
183
+ config.delete(jwtTokenConfigKey);
184
+ };
185
+ var oldConfigPath = `${import_xdg_basedir.default.config}/configstore/config.json.json`;
186
+ if (fs2.existsSync(oldConfigPath)) {
187
+ try {
188
+ const { email, accessToken, jwtToken } = require(oldConfigPath);
189
+ setAuthConfig(email, accessToken, jwtToken);
190
+ (0, import_del.default)(oldConfigPath, { force: true });
191
+ } catch (err) {
192
+ (0, import_del.default)(oldConfigPath, { force: true });
193
+ }
194
+ }
195
+
196
+ // src/utilities/getToDesktopPackageJson.ts
197
+ var import_pkg_up = __toESM(require("pkg-up"));
198
+
199
+ // src/utilities/readJson.ts
200
+ var readJson_default = (filePath) => require(filePath);
201
+
202
+ // src/utilities/getToDesktopPackageJson.ts
203
+ var packageJson = readJson_default(import_pkg_up.default.sync({ cwd: __dirname }));
204
+ function getToDesktopPackageJson() {
205
+ return packageJson;
206
+ }
207
+
208
+ // src/utilities/getCliVersion.ts
209
+ var cliVersion = getToDesktopPackageJson().version;
210
+ var getCliVersion_default = () => cliVersion;
211
+
212
+ // src/utilities/analytics.ts
213
+ var environmentVariables2 = getEnvironmentVariables_default();
214
+ var analytics = new import_analytics_node.default(environmentVariables2.SEGMENT_WRITE_KEY, {
215
+ flushAt: 1,
216
+ flushInterval: 1
217
+ });
218
+ var ANALYTICS_EVENT = {
219
+ CLI_COMMAND: "CLI Command"
220
+ };
221
+ function getAnalyticsBaseProperties() {
222
+ const baseProps = {
223
+ app: {
224
+ cliVersion: ""
225
+ },
226
+ client: "cli",
227
+ user: {
228
+ email: ""
229
+ },
230
+ os: {
231
+ name: "",
232
+ version: ""
233
+ },
234
+ runtime: {
235
+ name: "Node.js",
236
+ version: ""
237
+ }
238
+ };
239
+ try {
240
+ baseProps.os = {
241
+ name: import_os.default.platform(),
242
+ version: import_os.default.release()
243
+ };
244
+ } catch (err) {
245
+ }
246
+ try {
247
+ baseProps.user = {
248
+ email: getAuthConfig().email
249
+ };
250
+ } catch (err) {
251
+ }
252
+ try {
253
+ baseProps.app = {
254
+ cliVersion: getCliVersion_default()
255
+ };
256
+ } catch (err) {
257
+ }
258
+ try {
259
+ baseProps.runtime = {
260
+ name: "Node.js",
261
+ version: process.version
262
+ };
263
+ } catch (err) {
264
+ }
265
+ return baseProps;
266
+ }
267
+ var anonymousId = (0, import_uuid.v4)();
268
+ var track = (event, properties = {}, callback = () => {
269
+ }) => {
270
+ try {
271
+ const user = currentUser();
272
+ analytics.track(
273
+ {
274
+ event,
275
+ userId: user ? user.uid : void 0,
276
+ anonymousId,
277
+ properties: {
278
+ ...properties,
279
+ ...getAnalyticsBaseProperties()
280
+ }
281
+ },
282
+ callback
283
+ );
284
+ } catch (err) {
285
+ }
286
+ };
287
+ var identify = (id, traits = {}, callback = () => {
288
+ }) => {
289
+ try {
290
+ analytics.identify({ userId: id, anonymousId, traits }, callback);
291
+ } catch (err) {
292
+ }
293
+ };
294
+ var flush = (callback = () => {
295
+ }) => analytics.flush(callback);
296
+
297
+ // src/utilities/useExit.ts
298
+ var useExit_default = () => {
299
+ const { exit } = (0, import_ink.useApp)();
300
+ return (error = void 0) => {
301
+ logger_default.debug({ error }, "Exit called");
302
+ firestore_default.terminate().catch(
303
+ (e) => logger_default.error(e)
304
+ );
305
+ let timeoutId;
306
+ Promise.race([
307
+ new Promise((resolve4) => flush(() => resolve4())),
308
+ new Promise(
309
+ (resolve4) => timeoutId = setTimeout(() => resolve4(), 1e3)
310
+ )
311
+ ]).catch(() => {
312
+ }).finally(() => {
313
+ clearTimeout(timeoutId);
314
+ try {
315
+ exit();
316
+ } catch (e) {
317
+ logger_default.error(e);
318
+ }
319
+ setTimeout(() => process.exit(error ? 1 : 0), 10);
320
+ });
321
+ };
322
+ };
323
+
324
+ // src/components/BuildCompleteMessage.tsx
325
+ var import_path2 = __toESM(require("path"));
326
+ var import_jsx_runtime = require("react/jsx-runtime");
327
+ var addTrailingSlash = (url) => {
328
+ return url.endsWith("/") ? url : `${url}/`;
329
+ };
330
+ var BuildCompleteMessage = ({ build }) => {
331
+ const exit = useExit_default();
332
+ let url = build.standardUniversalDownloadUrl;
333
+ if (process.platform === "darwin") {
334
+ url = new URL(
335
+ import_path2.default.posix.join("mac", "zip", process.arch),
336
+ addTrailingSlash(url)
337
+ ).href;
338
+ }
339
+ logForCI_default(`Build complete! ${url}`);
340
+ (0, import_react.useEffect)(() => {
341
+ if (url) {
342
+ setTimeout(exit, 10);
343
+ }
344
+ }, [exit, url]);
345
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink2.Box, { marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink2.Text, { children: [
346
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink2.Text, { bold: true, children: "Build complete!" }),
347
+ " ",
348
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink_link.default, { fallback: false, url, children: url || "" })
349
+ ] }) });
350
+ };
351
+ BuildCompleteMessage.propTypes = {
352
+ build: import_prop_types.default.object.isRequired
353
+ };
354
+ var BuildCompleteMessage_default = BuildCompleteMessage;
355
+
356
+ // src/components/PlatformProgress.tsx
357
+ var import_prop_types2 = __toESM(require("prop-types"));
358
+ var import_ink3 = require("ink");
359
+ var import_ink_link2 = __toESM(require("ink-link"));
360
+ var import_path3 = __toESM(require("path"));
361
+ var import_jsx_runtime2 = require("react/jsx-runtime");
362
+ var preLabelMaxWidth = "More info: ".length;
363
+ var getProgressPercentageLabel = (percentage, activityType) => {
364
+ let backgroundColor = "white";
365
+ let color = "black";
366
+ if (activityType === "error") {
367
+ backgroundColor = "red";
368
+ } else if (percentage === 100) {
369
+ backgroundColor = "green";
370
+ color = "black";
371
+ }
372
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
373
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink3.Text, { backgroundColor, color, children: [
374
+ " ",
375
+ Math.round(percentage).toString().padStart(2, "0"),
376
+ "%",
377
+ " "
378
+ ] }),
379
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink3.Text, { children: " " })
380
+ ] });
381
+ };
382
+ var PlatformProgress = ({
383
+ activityName,
384
+ activityType,
385
+ downloadUrl,
386
+ percent,
387
+ platform,
388
+ shouldShowReadyPrefix
389
+ }) => {
390
+ let progressIndicator;
391
+ if (activityType === "done") {
392
+ if (process.platform === "darwin") {
393
+ downloadUrl = import_path3.default.posix.join(downloadUrl, "mac", "zip", process.arch);
394
+ }
395
+ progressIndicator = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink3.Box, { flexDirection: "column", marginBottom: 1, children: [
396
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink3.Text, { children: [
397
+ getProgressPercentageLabel(100),
398
+ shouldShowReadyPrefix ? "Ready!" : ""
399
+ ] }),
400
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink_link2.default, { fallback: false, url: downloadUrl, children: downloadUrl })
401
+ ] });
402
+ } else {
403
+ let activityNameToDisplay = activityName.replace(
404
+ /(?:\r\n|\r|\n)\s*/g,
405
+ "\u21B5 "
406
+ );
407
+ if (activityNameToDisplay.length > 64) {
408
+ activityNameToDisplay = `${activityNameToDisplay.substr(0, 61)}...`;
409
+ }
410
+ progressIndicator = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink3.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink3.Text, { children: [
411
+ ["preparation", "queue"].includes(activityType) ? void 0 : getProgressPercentageLabel(percent, activityType),
412
+ activityNameToDisplay,
413
+ activityType === "error" ? "" : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink3.Text, { children: "..." })
414
+ ] }) }) });
415
+ }
416
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink3.Box, { children: [
417
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink3.Box, { width: preLabelMaxWidth, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink3.Text, { children: [
418
+ platform[0].toUpperCase() + platform.substr(1),
419
+ ": "
420
+ ] }) }),
421
+ progressIndicator
422
+ ] });
423
+ };
424
+ PlatformProgress.propTypes = {
425
+ activityName: import_prop_types2.default.string.isRequired,
426
+ activityType: import_prop_types2.default.string.isRequired,
427
+ downloadUrl: import_prop_types2.default.string,
428
+ percent: import_prop_types2.default.number.isRequired,
429
+ platform: import_prop_types2.default.string.isRequired,
430
+ shouldShowReadyPrefix: import_prop_types2.default.bool
431
+ };
432
+ var PlatformProgress_default = PlatformProgress;
433
+
434
+ // src/utilities/buildStatus.ts
435
+ var hasBuildKickedOff = (build) => {
436
+ if (!build) {
437
+ return false;
438
+ }
439
+ return build.status && build.status !== "preparation";
440
+ };
441
+ var isBuildCancellable = (build) => hasBuildKickedOff(build) && isBuildRunning(build);
442
+ var isBuildRunning = (build) => {
443
+ if (!build) {
444
+ return false;
445
+ }
446
+ return !["cancelled", "succeeded"].includes(build.status) && ["linux", "mac", "windows"].some(
447
+ (platform) => isPlatformBuildRunning(build[platform])
448
+ );
449
+ };
450
+ var isPlatformBuildRunning = (platformBuild) => {
451
+ if (!platformBuild) {
452
+ return false;
453
+ }
454
+ return !platformBuild.shouldSkip && !["cancelled", "succeeded"].includes(platformBuild.status) && ("failed" !== platformBuild.status || platformBuild.numberOfAttemptedBuilds < 2);
455
+ };
456
+
457
+ // src/components/BuildProgress.tsx
458
+ var import_jsx_runtime3 = require("react/jsx-runtime");
459
+ var logForCIThrottled = (0, import_lodash.default)(logForCI_default, 60 * 1e3, { trailing: true });
460
+ var BuildProgress = ({ build, onBuildFailure }) => {
461
+ logger_default.debug("BuildProgress component: render");
462
+ const exit = useExit_default();
463
+ const [{ hasKickedOff }, setState] = (0, import_react2.useState)({
464
+ hasKickedOff: false
465
+ });
466
+ const platformsNotSkipped = ["windows", "mac", "linux"].filter(
467
+ (platform) => !build[platform].shouldSkip
468
+ );
469
+ (0, import_react2.useEffect)(() => {
470
+ setState((previousState) => ({
471
+ ...previousState,
472
+ hasKickedOff: hasBuildKickedOff(build)
473
+ }));
474
+ }, [build]);
475
+ (0, import_react2.useEffect)(() => {
476
+ if (build.status === "failed") {
477
+ onBuildFailure(build.errorMessage);
478
+ } else if (build.status === "cancelled") {
479
+ setTimeout(exit, 10);
480
+ } else {
481
+ const ciPlatformProgress = platformsNotSkipped.map(
482
+ (platform) => `${platform.charAt(0).toUpperCase() + platform.slice(1)}: ${build[platform].progressActivityName} (${build[platform].progressPercentage}%)`
483
+ );
484
+ logForCIThrottled(ciPlatformProgress.join(", "));
485
+ }
486
+ }, [build, exit, onBuildFailure, platformsNotSkipped]);
487
+ if (build.status === "succeeded") {
488
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(BuildCompleteMessage_default, { build });
489
+ }
490
+ if (build.status === "cancelled") {
491
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink4.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink4.Text, { children: "Build cancelled" }) });
492
+ }
493
+ if (!hasKickedOff) {
494
+ return null;
495
+ }
496
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink4.Box, { flexDirection: "column", children: platformsNotSkipped.map((platform) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink4.Box, { flexDirection: "column", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
497
+ PlatformProgress_default,
498
+ {
499
+ activityName: build[platform].status === "failed" ? build[platform].errorMessage : build[platform].progressActivityName,
500
+ activityType: build[platform].status === "failed" ? "error" : build[platform].progressActivityType,
501
+ downloadUrl: build[platform].standardDownloadUrl,
502
+ percent: build[platform].progressPercentage,
503
+ platform,
504
+ shouldShowReadyPrefix: !(build && ["failed", "succeeded"].includes(build.status))
505
+ }
506
+ ) }, platform)) }) });
507
+ };
508
+ BuildProgress.propTypes = {
509
+ build: import_prop_types3.default.object.isRequired,
510
+ onBuildFailure: import_prop_types3.default.func.isRequired
511
+ };
512
+ var BuildProgress_default = BuildProgress;
513
+
514
+ // src/components/ErrorDisplay.tsx
515
+ var import_ink5 = require("ink");
516
+ var import_prop_types4 = __toESM(require("prop-types"));
517
+ var import_react3 = require("react");
518
+ var import_jsx_runtime4 = require("react/jsx-runtime");
519
+ var ErrorDisplay = ({ commandUsed, error }) => {
520
+ const exit = useExit_default();
521
+ logger_default.error({ error });
522
+ (0, import_react3.useEffect)(() => {
523
+ if (!error) {
524
+ return;
525
+ }
526
+ setTimeout(() => exit(error), 10);
527
+ }, [exit, error]);
528
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
529
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Text, { backgroundColor: "red", color: "white", children: [
530
+ " ",
531
+ "ERROR",
532
+ " "
533
+ ] }),
534
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink5.Text, { children: error.message || error.stack || error.toString() }),
535
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Box, { flexDirection: "column", marginTop: 1, children: [
536
+ commandUsed ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Text, { dimColor: true, color: "gray", children: [
537
+ "Command: ",
538
+ commandUsed
539
+ ] }) : null,
540
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Text, { dimColor: true, color: "gray", children: [
541
+ "@todesktop/cli: ",
542
+ getCliVersion_default()
543
+ ] }),
544
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Text, { dimColor: true, color: "gray", children: [
545
+ "Node: ",
546
+ process.version
547
+ ] }),
548
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Text, { dimColor: true, color: "gray", children: [
549
+ "cwd: ",
550
+ process.cwd()
551
+ ] })
552
+ ] })
553
+ ] });
554
+ };
555
+ ErrorDisplay.propTypes = {
556
+ commandUsed: import_prop_types4.default.string,
557
+ error: import_prop_types4.default.object.isRequired
558
+ };
559
+ var ErrorDisplay_default = ErrorDisplay;
560
+
561
+ // src/components/Preparation.tsx
562
+ var import_ink7 = require("ink");
563
+
564
+ // src/components/ProgressBar.tsx
565
+ var import_prop_types5 = __toESM(require("prop-types"));
566
+ var import_ink_gradient = __toESM(require("ink-gradient"));
567
+
568
+ // src/libs/ink-progress-bar/index.tsx
569
+ var import_ink6 = require("ink");
570
+ var import_jsx_runtime5 = require("react/jsx-runtime");
571
+ var Bar = ({
572
+ percent = 1,
573
+ columns = 0,
574
+ left = 0,
575
+ right = 0,
576
+ character = "\u2588",
577
+ rightPad = false,
578
+ ...rest
579
+ }) => {
580
+ const getString = () => {
581
+ const screen = columns || process.stdout.columns || 80;
582
+ const space = screen - right - left;
583
+ const max = Math.min(Math.floor(space * percent), space);
584
+ const chars = character.repeat(max);
585
+ if (!rightPad) {
586
+ return chars;
587
+ }
588
+ return chars + " ".repeat(space - max);
589
+ };
590
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink6.Text, { ...rest, children: getString() });
591
+ };
592
+ var ink_progress_bar_default = Bar;
593
+
594
+ // src/components/ProgressBar.tsx
595
+ var import_jsx_runtime6 = require("react/jsx-runtime");
596
+ var ProgressBar = ({ left, right, percent }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink_gradient.default, { colors: ["gray", "white"], children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ink_progress_bar_default, { left, right, percent }) });
597
+ ProgressBar.propTypes = {
598
+ left: import_prop_types5.default.number,
599
+ right: import_prop_types5.default.number,
600
+ percent: import_prop_types5.default.number.isRequired
601
+ };
602
+ var ProgressBar_default = ProgressBar;
603
+
604
+ // src/components/Preparation.tsx
605
+ var import_prop_types6 = __toESM(require("prop-types"));
606
+ var import_jsx_runtime7 = require("react/jsx-runtime");
607
+ var Preparation = ({ progressPercentage, stageLabel, uploadedSize }) => {
608
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Box, { marginBottom: 1, children: [
609
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Text, { children: [
610
+ stageLabel,
611
+ ": "
612
+ ] }),
613
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Box, { flexDirection: "column", children: [
614
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ProgressBar_default, { left: 11, percent: progressPercentage }),
615
+ uploadedSize ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { children: uploadedSize }) : void 0
616
+ ] })
617
+ ] });
618
+ };
619
+ Preparation.propTypes = {
620
+ progressPercentage: import_prop_types6.default.number.isRequired,
621
+ stageLabel: import_prop_types6.default.string.isRequired,
622
+ uploadedSize: import_prop_types6.default.string
623
+ };
624
+ var Preparation_default = Preparation;
625
+
626
+ // src/components/InitialLoadingState.tsx
627
+ var import_ink8 = require("ink");
628
+ var import_jsx_runtime8 = require("react/jsx-runtime");
629
+ var InitialLoadingState = () => {
630
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { children: "Getting app info..." });
631
+ };
632
+ var InitialLoadingState_default = InitialLoadingState;
633
+
634
+ // src/components/MainLayout.tsx
635
+ var import_ink13 = require("ink");
636
+ var import_is_ci3 = __toESM(require("is-ci"));
637
+ var import_react5 = require("react");
638
+
639
+ // src/components/CancelBuild.tsx
640
+ var import_ink9 = require("ink");
641
+ var import_react4 = require("react");
642
+
643
+ // src/utilities/getCallableFirebaseFunction.ts
644
+ var import_firebase = __toESM(require("firebase"));
645
+ var import_functions = require("firebase/functions");
646
+ if (getEnvironmentVariables_default().TODESKTOP_CLI_ENV === "development") {
647
+ import_firebase.default.functions().useFunctionsEmulator("http://localhost:5000");
648
+ }
649
+ var getCallableFirebaseFunction_default = (functionName) => import_firebase.default.functions().httpsCallable(functionName);
650
+
651
+ // src/components/CancelBuild.tsx
652
+ var import_jsx_runtime9 = require("react/jsx-runtime");
653
+ var cancelBuild = getCallableFirebaseFunction_default("cancelBuild");
654
+ var CancelBuild = ({
655
+ appId,
656
+ children: postCancelContents,
657
+ commandUsed,
658
+ id
659
+ }) => {
660
+ logger_default.debug({ appId, buildId: id }, "CancelBuild component: render");
661
+ const exit = useExit_default();
662
+ const [
663
+ { arbitraryMessageComponent, error, hasCompleted, hasStarted },
664
+ setState
665
+ ] = (0, import_react4.useState)({
666
+ arbitraryMessageComponent: null,
667
+ error: null,
668
+ hasCompleted: false,
669
+ hasStarted: false
670
+ });
671
+ (0, import_react4.useEffect)(() => {
672
+ if (hasCompleted || hasStarted) {
673
+ return;
674
+ }
675
+ setState((previousState) => ({ ...previousState, hasStarted: true }));
676
+ logForCI_default("Cancelling build...");
677
+ cancelBuild({ appId, buildId: id }).then((result) => {
678
+ logger_default.debug(
679
+ { appId, buildId: id, result },
680
+ "CancelBuild component: cancellation request complete"
681
+ );
682
+ setState((previousState) => ({ ...previousState, hasCompleted: true }));
683
+ }).catch((e) => {
684
+ logger_default.debug(
685
+ { appId, buildId: id, code: e.code },
686
+ "CancelBuild component: cancellation request failed"
687
+ );
688
+ logger_default.error(e);
689
+ const stateUpdates = {
690
+ arbitraryMessageComponent: void 0,
691
+ error: void 0,
692
+ hasCompleted: true
693
+ };
694
+ if (e.code === "internal") {
695
+ stateUpdates.error = e;
696
+ } else {
697
+ stateUpdates.arbitraryMessageComponent = /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { children: [
698
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { color: "red", children: [
699
+ "Cannot cancel build (",
700
+ id,
701
+ ")."
702
+ ] }),
703
+ " ",
704
+ e.message
705
+ ] });
706
+ }
707
+ setState((prevState) => ({
708
+ ...prevState,
709
+ ...stateUpdates
710
+ }));
711
+ });
712
+ }, [appId, id, hasStarted, hasCompleted]);
713
+ (0, import_react4.useEffect)(() => {
714
+ if (hasCompleted && !postCancelContents) {
715
+ logger_default.debug(
716
+ "CancelBuild component: exiting now that build is cancelled"
717
+ );
718
+ exit();
719
+ }
720
+ }, [postCancelContents, exit, hasCompleted]);
721
+ if (error) {
722
+ error.message = `Failed to cancel build (${id}). ${error.message}`;
723
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ErrorDisplay_default, { commandUsed, error });
724
+ }
725
+ if (arbitraryMessageComponent) {
726
+ return arbitraryMessageComponent;
727
+ }
728
+ if (hasCompleted) {
729
+ return postCancelContents ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_jsx_runtime9.Fragment, { children: postCancelContents }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { children: "Build cancelled" });
730
+ }
731
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { children: [
732
+ "Cancelling build (",
733
+ id,
734
+ ")..."
735
+ ] });
736
+ };
737
+ var CancelBuild_default = CancelBuild;
738
+
739
+ // src/components/Footer.tsx
740
+ var import_ink10 = require("ink");
741
+ var import_ink_link3 = __toESM(require("ink-link"));
742
+ var import_prop_types7 = __toESM(require("prop-types"));
743
+ var import_jsx_runtime10 = require("react/jsx-runtime");
744
+ var Footer = ({
745
+ hasBuildEverFailed,
746
+ shouldShowCancelBuildInstructions,
747
+ uiUrl
748
+ }) => {
749
+ if (!uiUrl) {
750
+ return null;
751
+ }
752
+ let buildFailedMessage;
753
+ if (hasBuildEverFailed) {
754
+ buildFailedMessage = /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { color: "red", children: "An error has occurred. " });
755
+ }
756
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", children: [
757
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", children: [
758
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Text, { bold: true, children: [
759
+ buildFailedMessage,
760
+ "See web UI for more information: "
761
+ ] }),
762
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink_link3.default, { fallback: false, url: uiUrl, children: uiUrl }) })
763
+ ] }),
764
+ shouldShowCancelBuildInstructions ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Text, { color: "gray", children: [
765
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { bold: true, children: "[esc]:" }),
766
+ " cancel build"
767
+ ] }) : null
768
+ ] });
769
+ };
770
+ Footer.propTypes = {
771
+ hasBuildEverFailed: import_prop_types7.default.bool,
772
+ shouldShowCancelBuildInstructions: import_prop_types7.default.bool,
773
+ uiUrl: import_prop_types7.default.string
774
+ };
775
+ var Footer_default = Footer;
776
+
777
+ // src/components/Header.tsx
778
+ var import_ink11 = require("ink");
779
+ var import_prop_types8 = __toESM(require("prop-types"));
780
+ var import_jsx_runtime11 = require("react/jsx-runtime");
781
+ var getText = (build, name, version) => {
782
+ const suffix = name + (version ? ` v${version}` : "");
783
+ if (build && ["cancelled", "succeeded", "failed"].includes(build.status)) {
784
+ if (build.status === "succeeded") {
785
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Text, { children: [
786
+ "\u2705 ",
787
+ suffix
788
+ ] });
789
+ } else if (build.status === "failed") {
790
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Text, { children: [
791
+ "\u274C ",
792
+ suffix
793
+ ] });
794
+ } else if (build.status === "cancelled") {
795
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Text, { children: [
796
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { bold: true, color: "gray", children: "X" }),
797
+ " ",
798
+ suffix
799
+ ] });
800
+ }
801
+ } else {
802
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { children: suffix });
803
+ }
804
+ };
805
+ var Header = ({ build, name, version }) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Box, { marginBottom: 1, children: getText(build, name, version) });
806
+ Header.propTypes = {
807
+ build: import_prop_types8.default.object,
808
+ name: import_prop_types8.default.string.isRequired,
809
+ version: import_prop_types8.default.string
810
+ };
811
+ var Header_default = Header;
812
+
813
+ // src/utilities/useInput.ts
814
+ var import_is_ci2 = __toESM(require("is-ci"));
815
+ var import_ink12 = require("ink");
816
+ var hasSubscribed = false;
817
+ var ctrlCSubscriptions = [];
818
+ var onStdin = (data, exit) => {
819
+ let input = String(data);
820
+ let wasCtrlPressed = false;
821
+ if (input <= "" && input !== "\r") {
822
+ input = String.fromCharCode(input.charCodeAt(0) + "a".charCodeAt(0) - 1);
823
+ wasCtrlPressed = true;
824
+ }
825
+ if (wasCtrlPressed && input === "c") {
826
+ ctrlCSubscriptions.forEach((subscription) => subscription());
827
+ setTimeout(exit, 10);
828
+ }
829
+ };
830
+ var useInput_default = () => {
831
+ const exit = useExit_default();
832
+ const { stdin, isRawModeSupported } = (0, import_ink12.useStdin)();
833
+ if (import_is_ci2.default || !isRawModeSupported) {
834
+ return () => () => {
835
+ };
836
+ }
837
+ return (callback, useInputOptions = {}, { onCtrlCPressed = null } = {}) => {
838
+ const onStdinData = (data) => onStdin(data, exit);
839
+ if (onCtrlCPressed) {
840
+ ctrlCSubscriptions.push(onCtrlCPressed);
841
+ }
842
+ if (!hasSubscribed) {
843
+ hasSubscribed = true;
844
+ stdin.on("data", onStdinData);
845
+ }
846
+ const cleanUpInputHook = (0, import_ink12.useInput)(callback, useInputOptions);
847
+ return () => {
848
+ if (hasSubscribed) {
849
+ stdin.off("data", onStdinData);
850
+ }
851
+ cleanUpInputHook == null ? void 0 : cleanUpInputHook();
852
+ };
853
+ };
854
+ };
855
+
856
+ // src/components/MainLayout.tsx
857
+ var import_jsx_runtime12 = require("react/jsx-runtime");
858
+ var MainLayout = ({
859
+ appId,
860
+ appName,
861
+ appVersion,
862
+ build,
863
+ children,
864
+ commandUsed,
865
+ hasBuildEverFailed
866
+ }) => {
867
+ logger_default.debug("MainLayout component: render");
868
+ const onInput = useInput_default();
869
+ const { isRawModeSupported } = (0, import_ink13.useStdin)();
870
+ const [
871
+ { canCancelBuild, hasKickedOff, isCancellingBuild, wasCtrlCPressed },
872
+ setState
873
+ ] = (0, import_react5.useState)({
874
+ canCancelBuild: null,
875
+ hasKickedOff: false,
876
+ isCancellingBuild: false,
877
+ wasCtrlCPressed: false
878
+ });
879
+ (0, import_react5.useEffect)(() => {
880
+ setState((previousState) => ({
881
+ ...previousState,
882
+ hasKickedOff: hasBuildKickedOff(build)
883
+ }));
884
+ }, [build]);
885
+ (0, import_react5.useEffect)(() => {
886
+ setState((previousState) => ({
887
+ ...previousState,
888
+ canCancelBuild: !import_is_ci3.default && isRawModeSupported && !isCancellingBuild && isBuildCancellable(build)
889
+ }));
890
+ }, [build, isCancellingBuild]);
891
+ onInput(
892
+ async (input, key) => {
893
+ if (key.escape && canCancelBuild) {
894
+ logger_default.debug("MainLayout component: esc pressed, cancelling build");
895
+ setState((previousState) => ({
896
+ ...previousState,
897
+ isCancellingBuild: true
898
+ }));
899
+ }
900
+ },
901
+ {},
902
+ {
903
+ onCtrlCPressed: () => {
904
+ setState((previousState) => ({
905
+ ...previousState,
906
+ wasCtrlCPressed: true
907
+ }));
908
+ }
909
+ }
910
+ );
911
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
912
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Header_default, { build, name: appName, version: appVersion }),
913
+ isCancellingBuild ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink13.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(CancelBuild_default, { appId, commandUsed, id: build.id }) }) : children,
914
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
915
+ Footer_default,
916
+ {
917
+ hasBuildEverFailed,
918
+ shouldShowCancelBuildInstructions: canCancelBuild && !wasCtrlCPressed,
919
+ uiUrl: build ? build.url : null
920
+ }
921
+ ),
922
+ wasCtrlCPressed && hasKickedOff ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
923
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink13.Box, { flexDirection: "column", marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink13.Text, { color: "gray", children: "The build will continue in the background. To view it, run:" }) }),
924
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink13.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_ink13.Text, { bold: true, color: "gray", children: [
925
+ "todesktop builds ",
926
+ build.id
927
+ ] }) })
928
+ ] }) : null
929
+ ] });
930
+ };
931
+ var MainLayout_default = MainLayout;
932
+
933
+ // src/utilities/runBuild.ts
934
+ var import_pretty_bytes = __toESM(require("pretty-bytes"));
935
+
936
+ // src/utilities/getVersionControlInfo.ts
937
+ var gitRevSync = __toESM(require("git-rev-sync"));
938
+ var getVersionControlInfo_default = async (directory) => {
939
+ let result = {};
940
+ try {
941
+ const gitCommitHash = gitRevSync.long(directory);
942
+ if (gitCommitHash) {
943
+ result = {
944
+ branchName: gitRevSync.branch(directory),
945
+ commitDate: gitRevSync.date().toISOString(),
946
+ commitId: gitCommitHash,
947
+ commitMessage: gitRevSync.message(),
948
+ hasUncommittedChanges: gitRevSync.isDirty(),
949
+ repositoryRemoteUrl: gitRevSync.remoteUrl(),
950
+ versionControlSystemName: "git"
951
+ };
952
+ }
953
+ } catch (e) {
954
+ }
955
+ return result;
956
+ };
957
+
958
+ // src/utilities/projectConfig/getProjectConfig.ts
959
+ var import_path5 = require("path");
960
+ var import_fs = require("fs");
961
+ var import_find_up = __toESM(require("find-up"));
962
+
963
+ // src/utilities/projectConfig/loadConfig.ts
964
+ var loadConfig_default = (configPath) => require(configPath);
965
+
966
+ // src/utilities/projectConfig/resolveConfigPaths.ts
967
+ var path5 = __toESM(require("path"));
968
+ var resolveConfigPaths_default = ({ config: config2, projectRoot }) => {
969
+ const appRoot = config2.appPath ? path5.isAbsolute(config2.appPath) ? config2.appPath : path5.join(projectRoot, config2.appPath) : projectRoot;
970
+ const transformIfExists = (value, transformer) => value ? transformer(value) : void 0;
971
+ const resolvePath = (filePath) => path5.isAbsolute(filePath) ? filePath : path5.join(projectRoot, filePath);
972
+ const result = {
973
+ ...config2,
974
+ appPath: appRoot,
975
+ icon: resolvePath(config2.icon)
976
+ };
977
+ if (config2.extraContentFiles) {
978
+ result.extraContentFiles = transformIfExists(
979
+ config2.extraContentFiles,
980
+ (extraContentFiles) => extraContentFiles.map((extraContentFile) => ({
981
+ ...extraContentFile,
982
+ from: resolvePath(extraContentFile.from)
983
+ }))
984
+ );
985
+ }
986
+ if (config2.extraResources) {
987
+ result.extraResources = transformIfExists(
988
+ config2.extraResources,
989
+ (extraResources) => extraResources.map((extraResource) => ({
990
+ ...extraResource,
991
+ from: resolvePath(extraResource.from)
992
+ }))
993
+ );
994
+ }
995
+ if (config2.linux) {
996
+ result.linux = { ...config2.linux };
997
+ if (config2.linux.icon) {
998
+ result.linux.icon = resolvePath(config2.linux.icon);
999
+ }
1000
+ }
1001
+ if (config2.mac) {
1002
+ result.mac = { ...config2.mac };
1003
+ if (config2.mac.entitlements) {
1004
+ result.mac.entitlements = resolvePath(config2.mac.entitlements);
1005
+ }
1006
+ if (config2.mac.icon) {
1007
+ result.mac.icon = resolvePath(config2.mac.icon);
1008
+ }
1009
+ }
1010
+ if (config2.dmg) {
1011
+ if (config2.dmg.background) {
1012
+ result.dmg.background = resolvePath(config2.dmg.background);
1013
+ }
1014
+ }
1015
+ if (config2.windows) {
1016
+ result.windows = { ...config2.windows };
1017
+ if (config2.windows.icon) {
1018
+ result.windows.icon = resolvePath(config2.windows.icon);
1019
+ }
1020
+ }
1021
+ return result;
1022
+ };
1023
+
1024
+ // src/utilities/projectConfig/validateConfig.ts
1025
+ var import_ajv3 = __toESM(require("ajv"));
1026
+ var import_ajv_formats2 = __toESM(require("ajv-formats"));
1027
+ var import_better_ajv_errors2 = __toESM(require("better-ajv-errors"));
1028
+
1029
+ // src/utilities/projectConfig/addCustomKeywords.ts
1030
+ var import_ajv2 = require("ajv");
1031
+ var import_email_regex = __toESM(require("email-regex"));
1032
+ var fs3 = __toESM(require("fs"));
1033
+ var path6 = __toESM(require("path"));
1034
+ var import_parse_author = __toESM(require("parse-author"));
1035
+ var semver = __toESM(require("semver"));
1036
+
1037
+ // src/utilities/projectConfig/validatePackageJSON.ts
1038
+ var import_ajv = __toESM(require("ajv"));
1039
+ var import_ajv_formats = __toESM(require("ajv-formats"));
1040
+ var import_better_ajv_errors = __toESM(require("better-ajv-errors"));
1041
+
1042
+ // src/utilities/projectConfig/schema/packageJSON.ts
1043
+ var packageJSON_default = (context) => {
1044
+ return {
1045
+ type: "object",
1046
+ required: [
1047
+ "author",
1048
+ "dependencies",
1049
+ "devDependencies",
1050
+ "homepage",
1051
+ "name",
1052
+ "version"
1053
+ ],
1054
+ properties: {
1055
+ author: {
1056
+ npmAuthor: {}
1057
+ },
1058
+ dependencies: {
1059
+ type: "object",
1060
+ required: ["@todesktop/runtime"],
1061
+ excludedDependencies: {
1062
+ dependencyKey: "dependencies",
1063
+ blacklist: ["@todesktop/cli"]
1064
+ },
1065
+ properties: {
1066
+ "@todesktop/runtime": {
1067
+ type: "string"
1068
+ }
1069
+ }
1070
+ },
1071
+ devDependencies: {
1072
+ type: "object",
1073
+ required: ["electron"],
1074
+ properties: {
1075
+ electron: {
1076
+ type: "string",
1077
+ semanticVersion: {
1078
+ packageName: "electron",
1079
+ mustBeExact: true
1080
+ }
1081
+ }
1082
+ }
1083
+ },
1084
+ name: {
1085
+ type: "string",
1086
+ minLength: 1
1087
+ },
1088
+ scripts: {
1089
+ type: "object",
1090
+ properties: {
1091
+ "todesktop:beforeInstall": {
1092
+ type: "string",
1093
+ file: {
1094
+ from: context.projectRoot,
1095
+ mustBeFile: true
1096
+ }
1097
+ },
1098
+ "todesktop:afterPack": {
1099
+ type: "string",
1100
+ file: {
1101
+ from: context.projectRoot,
1102
+ mustBeFile: true
1103
+ }
1104
+ }
1105
+ }
1106
+ },
1107
+ version: {
1108
+ type: "string",
1109
+ semanticVersion: {}
1110
+ }
1111
+ }
1112
+ };
1113
+ };
1114
+
1115
+ // src/utilities/projectConfig/validatePackageJSON.ts
1116
+ var validatePackageJSON_default = (pkg, pkgPath, context) => {
1117
+ const ajv = new import_ajv.default({ allErrors: true });
1118
+ (0, import_ajv_formats.default)(ajv);
1119
+ addCustomKeywords_default(ajv, context);
1120
+ const schema = packageJSON_default(context);
1121
+ const validate = ajv.compile(schema);
1122
+ if (!validate(pkg)) {
1123
+ const output = (0, import_better_ajv_errors.default)(schema, pkg, validate.errors, {
1124
+ indent: 2
1125
+ });
1126
+ const error = new Error(`package.json invalid (${pkgPath});
1127
+ ${output}`);
1128
+ error.isValidationError = true;
1129
+ throw error;
1130
+ }
1131
+ };
1132
+
1133
+ // src/utilities/projectConfig/addCustomKeywords.ts
1134
+ var addCustomKeywords_default = (ajv, context) => {
1135
+ const addKeyword = (def) => {
1136
+ const validate = (schema, data) => {
1137
+ try {
1138
+ return def.validate(schema, data);
1139
+ } catch (e) {
1140
+ if (e instanceof import_ajv2.ValidationError) {
1141
+ validate.errors = e.errors;
1142
+ return false;
1143
+ }
1144
+ throw e;
1145
+ }
1146
+ };
1147
+ ajv.addKeyword({ ...def, validate });
1148
+ };
1149
+ addKeyword({
1150
+ keyword: "excludedDependencies",
1151
+ validate: (schema, data) => {
1152
+ const matchingPackages = schema.blacklist.filter(
1153
+ (packageName) => data[packageName]
1154
+ );
1155
+ if (matchingPackages.length) {
1156
+ throw new import_ajv2.ValidationError([
1157
+ {
1158
+ keyword: "Dependency",
1159
+ message: `${matchingPackages.join(", ")} should not be in ${schema.dependencyKey}`,
1160
+ instancePath: `/${schema.dependencyKey}`
1161
+ }
1162
+ ]);
1163
+ }
1164
+ return true;
1165
+ }
1166
+ });
1167
+ addKeyword({
1168
+ keyword: "email",
1169
+ type: "string",
1170
+ validate: (schema, data) => {
1171
+ if (!(0, import_email_regex.default)({ exact: true }).test(data)) {
1172
+ throw new import_ajv2.ValidationError([
1173
+ {
1174
+ keyword: "email",
1175
+ message: "invalid"
1176
+ }
1177
+ ]);
1178
+ }
1179
+ return true;
1180
+ }
1181
+ });
1182
+ addKeyword({
1183
+ keyword: "file",
1184
+ type: "string",
1185
+ validate: (schema, data) => {
1186
+ const mustBeDirectory = schema.mustBeDirectory || schema.mustBeElectronApp;
1187
+ if (!data.trim().length) {
1188
+ if (schema.isOptional) {
1189
+ return true;
1190
+ }
1191
+ throw new import_ajv2.ValidationError([
1192
+ {
1193
+ keyword: schema.mustBeDirectory ? "Directory" : "File",
1194
+ message: "is empty"
1195
+ }
1196
+ ]);
1197
+ }
1198
+ const filePath = path6.isAbsolute(data) ? data : path6.join(schema.from, data);
1199
+ if (path6.relative(schema.from, filePath).startsWith("..")) {
1200
+ throw new import_ajv2.ValidationError([
1201
+ {
1202
+ keyword: mustBeDirectory ? "Directory" : "File",
1203
+ message: `is not in project (${filePath})`
1204
+ }
1205
+ ]);
1206
+ }
1207
+ if (schema.extensions && !schema.extensions.includes(path6.extname(filePath).substr(1))) {
1208
+ throw new import_ajv2.ValidationError([
1209
+ {
1210
+ keyword: "File extension",
1211
+ message: `invalid. ${path6.extname(data)} Must be${schema.extensions > 1 ? "one of the following:" : ""} "${schema.extensions.join('", "')}"`
1212
+ }
1213
+ ]);
1214
+ }
1215
+ if (!fs3.existsSync(filePath)) {
1216
+ throw new import_ajv2.ValidationError([
1217
+ {
1218
+ keyword: mustBeDirectory ? "Directory" : "File",
1219
+ message: `does not exist (${filePath})`
1220
+ }
1221
+ ]);
1222
+ }
1223
+ const stats = fs3.statSync(filePath);
1224
+ if (mustBeDirectory && stats.isFile()) {
1225
+ throw new import_ajv2.ValidationError([
1226
+ {
1227
+ keyword: "Directory",
1228
+ message: `must be a directory, not a file (${filePath})`
1229
+ }
1230
+ ]);
1231
+ } else if (schema.mustBeFile && stats.isDirectory()) {
1232
+ throw new import_ajv2.ValidationError([
1233
+ {
1234
+ keyword: "File",
1235
+ message: `must be a file, not a directory (${filePath})`
1236
+ }
1237
+ ]);
1238
+ }
1239
+ if (schema.mustBeElectronApp) {
1240
+ const appRoot = path6.resolve(filePath);
1241
+ const pkgPath = path6.join(appRoot, "package.json");
1242
+ if (!fs3.existsSync(pkgPath)) {
1243
+ throw new import_ajv2.ValidationError([
1244
+ {
1245
+ keyword: "App",
1246
+ message: `invalid. There is no package.json at ${pkgPath}`
1247
+ }
1248
+ ]);
1249
+ }
1250
+ let pkg;
1251
+ try {
1252
+ pkg = require(pkgPath);
1253
+ } catch (e) {
1254
+ throw new import_ajv2.ValidationError([
1255
+ {
1256
+ keyword: "App",
1257
+ message: `invalid. Invalid JSON in package.json (${pkgPath})`
1258
+ }
1259
+ ]);
1260
+ }
1261
+ context.projectRoot = appRoot;
1262
+ validatePackageJSON_default(pkg, pkgPath, context);
1263
+ const mainFilePath = pkg.main;
1264
+ if (mainFilePath) {
1265
+ const resolvedMainFilePath = path6.join(appRoot, mainFilePath);
1266
+ if (!fs3.existsSync(resolvedMainFilePath)) {
1267
+ throw new import_ajv2.ValidationError([
1268
+ {
1269
+ keyword: "App",
1270
+ message: `invalid. The "main" file specified in your package.json (${mainFilePath}) does not exist (${resolvedMainFilePath})`
1271
+ }
1272
+ ]);
1273
+ }
1274
+ } else {
1275
+ if (!fs3.existsSync(path6.join(appRoot, "index.js"))) {
1276
+ throw new import_ajv2.ValidationError([
1277
+ {
1278
+ keyword: "App",
1279
+ message: 'invalid. There is neither a "main" property in your package.json nor an index.js at the root of your app'
1280
+ }
1281
+ ]);
1282
+ }
1283
+ }
1284
+ }
1285
+ return true;
1286
+ },
1287
+ errors: true
1288
+ });
1289
+ addKeyword({
1290
+ keyword: "npmAuthor",
1291
+ validate: (schema, data) => {
1292
+ const dataType = typeof data;
1293
+ if (dataType === "undefined") {
1294
+ return true;
1295
+ } else if (dataType === "string") {
1296
+ const parsedAuthor = (0, import_parse_author.default)(data);
1297
+ if (!parsedAuthor || !parsedAuthor.email) {
1298
+ throw new import_ajv2.ValidationError([
1299
+ {
1300
+ keyword: "author",
1301
+ message: `invalid. If you're using a string, it must look something like "Barney Rubble <b@rubble.com>", see https://docs.npmjs.com/files/package.json#people-fields-author-contributors`
1302
+ }
1303
+ ]);
1304
+ }
1305
+ } else if (dataType === "object") {
1306
+ if (typeof data.name === "undefined") {
1307
+ throw new import_ajv2.ValidationError([
1308
+ {
1309
+ keyword: "author",
1310
+ message: `invalid. "name" property is missing.`
1311
+ }
1312
+ ]);
1313
+ } else {
1314
+ if (typeof data.name !== "string") {
1315
+ throw new import_ajv2.ValidationError([
1316
+ {
1317
+ keyword: "author",
1318
+ message: `.name invalid. It must be a string`
1319
+ }
1320
+ ]);
1321
+ }
1322
+ if (typeof data.email === "undefined") {
1323
+ throw new import_ajv2.ValidationError([
1324
+ {
1325
+ keyword: "author",
1326
+ message: `invalid. "email" property is missing.`
1327
+ }
1328
+ ]);
1329
+ } else {
1330
+ if (typeof data.email === "string") {
1331
+ if (!(0, import_email_regex.default)({ exact: true }).test(data.email)) {
1332
+ throw new import_ajv2.ValidationError([
1333
+ {
1334
+ keyword: "author",
1335
+ message: `.email invalid. It does not look like an email.`
1336
+ }
1337
+ ]);
1338
+ }
1339
+ } else {
1340
+ throw new import_ajv2.ValidationError([
1341
+ {
1342
+ keyword: "author",
1343
+ message: `.email invalid. It must be a string.`
1344
+ }
1345
+ ]);
1346
+ }
1347
+ }
1348
+ }
1349
+ } else {
1350
+ throw new import_ajv2.ValidationError([
1351
+ {
1352
+ keyword: "author",
1353
+ message: "invalid. It must either be an object or string. See https://docs.npmjs.com/files/package.json#people-fields-author-contributors"
1354
+ }
1355
+ ]);
1356
+ }
1357
+ return true;
1358
+ },
1359
+ errors: true
1360
+ });
1361
+ addKeyword({
1362
+ keyword: "semanticVersion",
1363
+ type: "string",
1364
+ validate: (schema, data) => {
1365
+ const keyword = (schema.packageName ? `${schema.packageName} ` : "") + "version";
1366
+ if (!(semver.valid(data) || semver.validRange(data))) {
1367
+ throw new import_ajv2.ValidationError([
1368
+ {
1369
+ keyword,
1370
+ message: "invalid. It must be a semantic version (see https://semver.org/)"
1371
+ }
1372
+ ]);
1373
+ }
1374
+ if (schema.mustBeExact && (data.startsWith("~") || data.startsWith("^"))) {
1375
+ throw new import_ajv2.ValidationError([
1376
+ {
1377
+ keyword,
1378
+ message: "invalid. It must be an exact version. Good: 9.0.0. Bad: ^9.0.0"
1379
+ }
1380
+ ]);
1381
+ }
1382
+ return true;
1383
+ }
1384
+ });
1385
+ };
1386
+
1387
+ // src/utilities/projectConfig/schema/full.ts
1388
+ var getItemOrArrayOfItemsSchema = (itemSchema) => {
1389
+ return {
1390
+ oneOf: [
1391
+ itemSchema,
1392
+ {
1393
+ type: "array",
1394
+ items: itemSchema
1395
+ }
1396
+ ]
1397
+ };
1398
+ };
1399
+ var full_default = (context) => {
1400
+ const getIconSchema = (additionalAllowedExtensions = []) => {
1401
+ return {
1402
+ type: "string",
1403
+ file: {
1404
+ from: context.projectRoot,
1405
+ extensions: ["icns", "png", ...additionalAllowedExtensions],
1406
+ mustBeFile: true
1407
+ },
1408
+ minLength: 3
1409
+ };
1410
+ };
1411
+ return {
1412
+ type: "object",
1413
+ required: ["id", "icon", "schemaVersion"],
1414
+ properties: {
1415
+ appId: { type: "string", minLength: 1 },
1416
+ appFiles: {
1417
+ type: "array",
1418
+ items: {
1419
+ type: "string",
1420
+ minLength: 1
1421
+ },
1422
+ minItems: 1
1423
+ },
1424
+ appProtocolScheme: { type: "string", minLength: 1 },
1425
+ appPath: {
1426
+ type: "string",
1427
+ file: {
1428
+ from: context.projectRoot,
1429
+ isOptional: true,
1430
+ mustBeElectronApp: true
1431
+ }
1432
+ },
1433
+ asarUnpack: {
1434
+ oneOf: [
1435
+ { type: "boolean" },
1436
+ {
1437
+ type: "array",
1438
+ items: {
1439
+ type: "string",
1440
+ minLength: 1
1441
+ },
1442
+ minItems: 1
1443
+ }
1444
+ ]
1445
+ },
1446
+ copyright: { type: "string", minLength: 1 },
1447
+ electronMirror: {
1448
+ type: "string",
1449
+ format: "uri"
1450
+ },
1451
+ electronVersion: {
1452
+ type: "string",
1453
+ minLength: 1
1454
+ },
1455
+ extraContentFiles: {
1456
+ type: "array",
1457
+ items: {
1458
+ type: "object",
1459
+ required: ["from"],
1460
+ properties: {
1461
+ from: {
1462
+ type: "string",
1463
+ file: { from: context.projectRoot }
1464
+ },
1465
+ to: { type: "string" }
1466
+ }
1467
+ }
1468
+ },
1469
+ extraResources: {
1470
+ type: "array",
1471
+ items: {
1472
+ type: "object",
1473
+ required: ["from"],
1474
+ properties: {
1475
+ from: {
1476
+ type: "string",
1477
+ file: { from: context.projectRoot }
1478
+ },
1479
+ to: { type: "string" }
1480
+ }
1481
+ }
1482
+ },
1483
+ fileAssociations: {
1484
+ type: "array",
1485
+ items: {
1486
+ type: "object",
1487
+ required: ["ext"],
1488
+ properties: {
1489
+ ext: getItemOrArrayOfItemsSchema({
1490
+ type: "string",
1491
+ minLength: 1
1492
+ }),
1493
+ description: {
1494
+ type: "string",
1495
+ minLength: 1
1496
+ },
1497
+ name: {
1498
+ type: "string",
1499
+ minLength: 1
1500
+ },
1501
+ mimeType: {
1502
+ type: "string",
1503
+ minLength: 1
1504
+ },
1505
+ icon: {
1506
+ type: "string",
1507
+ minLength: 1
1508
+ },
1509
+ role: {
1510
+ type: "string",
1511
+ minLength: 1
1512
+ },
1513
+ isPackage: {
1514
+ type: "boolean"
1515
+ },
1516
+ rank: {
1517
+ type: "string",
1518
+ minLength: 1
1519
+ }
1520
+ }
1521
+ },
1522
+ minItems: 1
1523
+ },
1524
+ filesForDistribution: {
1525
+ type: "array",
1526
+ items: {
1527
+ type: "string",
1528
+ minLength: 1
1529
+ },
1530
+ minItems: 1
1531
+ },
1532
+ icon: getIconSchema(),
1533
+ linux: {
1534
+ type: "object",
1535
+ properties: {
1536
+ category: { type: "string", minLength: 1 },
1537
+ icon: getIconSchema(),
1538
+ noSandbox: { type: "boolean" }
1539
+ }
1540
+ },
1541
+ id: { type: "string", minLength: 1 },
1542
+ mac: {
1543
+ type: "object",
1544
+ properties: {
1545
+ category: { type: "string", minLength: 1 },
1546
+ additionalBinariesToSign: {
1547
+ type: "array",
1548
+ items: {
1549
+ type: "string",
1550
+ minLength: 1
1551
+ }
1552
+ },
1553
+ entitlements: {
1554
+ type: "string",
1555
+ file: {
1556
+ from: context.projectRoot,
1557
+ extensions: ["plist"],
1558
+ mustBeFile: true
1559
+ },
1560
+ minLength: 1
1561
+ },
1562
+ extendInfo: {
1563
+ type: "object"
1564
+ },
1565
+ icon: getIconSchema()
1566
+ }
1567
+ },
1568
+ dmg: {
1569
+ type: "object",
1570
+ properties: {
1571
+ background: {
1572
+ type: "string",
1573
+ file: {
1574
+ from: context.projectRoot,
1575
+ extensions: ["tiff"],
1576
+ mustBeFile: true
1577
+ },
1578
+ minLength: 1
1579
+ },
1580
+ artifactName: { type: "string", minLength: 1 },
1581
+ backgroundColor: { type: "string", minLength: 1 },
1582
+ iconSize: { type: "number" },
1583
+ iconTextSize: { type: "number" },
1584
+ title: { type: "string", minLength: 1 },
1585
+ contents: {
1586
+ type: "array",
1587
+ items: {
1588
+ type: "object",
1589
+ properties: {
1590
+ x: { type: "number" },
1591
+ y: { type: "number" }
1592
+ }
1593
+ }
1594
+ },
1595
+ window: {
1596
+ type: "object",
1597
+ properties: {
1598
+ x: { type: "number" },
1599
+ y: { type: "number" },
1600
+ width: { type: "number" },
1601
+ height: { type: "number" }
1602
+ }
1603
+ }
1604
+ }
1605
+ },
1606
+ schemaVersion: { type: "number", minimum: 1, maximum: 1 },
1607
+ snap: {
1608
+ type: "object",
1609
+ properties: {
1610
+ after: {
1611
+ type: "array",
1612
+ items: {
1613
+ type: "string",
1614
+ minLength: 1
1615
+ }
1616
+ },
1617
+ appPartStage: {
1618
+ type: "array",
1619
+ items: {
1620
+ type: "string",
1621
+ minLength: 1
1622
+ }
1623
+ },
1624
+ assumes: getItemOrArrayOfItemsSchema({
1625
+ type: "string",
1626
+ minLength: 1
1627
+ }),
1628
+ autoStart: {
1629
+ type: "boolean"
1630
+ },
1631
+ buildPackages: {
1632
+ type: "array",
1633
+ items: {
1634
+ type: "string",
1635
+ minLength: 1
1636
+ }
1637
+ },
1638
+ confinement: {
1639
+ type: "string",
1640
+ enum: ["classic", "devmode", "strict"]
1641
+ },
1642
+ environment: {
1643
+ type: "object"
1644
+ },
1645
+ grade: {
1646
+ type: "string",
1647
+ enum: ["devel", "stable"]
1648
+ },
1649
+ layout: {
1650
+ type: "object"
1651
+ },
1652
+ plugs: {
1653
+ type: "array",
1654
+ items: {
1655
+ anyOf: [
1656
+ {
1657
+ type: "string",
1658
+ minLength: 1
1659
+ },
1660
+ {
1661
+ type: "object"
1662
+ }
1663
+ ]
1664
+ }
1665
+ },
1666
+ stagePackages: {
1667
+ type: "array",
1668
+ items: {
1669
+ type: "string",
1670
+ minLength: 1
1671
+ }
1672
+ },
1673
+ summary: {
1674
+ type: "string",
1675
+ maxLength: 78
1676
+ },
1677
+ useTemplateApp: {
1678
+ type: "boolean"
1679
+ }
1680
+ }
1681
+ },
1682
+ uploadSizeLimit: {
1683
+ type: "number"
1684
+ },
1685
+ windows: {
1686
+ type: "object",
1687
+ properties: {
1688
+ icon: getIconSchema(["ico"])
1689
+ }
1690
+ }
1691
+ }
1692
+ };
1693
+ };
1694
+
1695
+ // src/utilities/projectConfig/validateConfig.ts
1696
+ var validateConfig_default = ({ config: config2, projectRoot }) => {
1697
+ const context = { projectRoot };
1698
+ const schema = full_default(context);
1699
+ const ajv = new import_ajv3.default({ allErrors: true });
1700
+ (0, import_ajv_formats2.default)(ajv);
1701
+ addCustomKeywords_default(ajv, context);
1702
+ const validate = ajv.compile(schema);
1703
+ if (!validate(config2)) {
1704
+ const output = (0, import_better_ajv_errors2.default)(schema, config2, validate.errors, {
1705
+ indent: 2
1706
+ });
1707
+ throw new Error(
1708
+ `todesktop.json invalid.
1709
+ Learn more here: https://www.npmjs.com/package/@todesktop/cli#project-configuration-todesktopjson
1710
+
1711
+ ${output}`
1712
+ );
1713
+ }
1714
+ if (config2.productName) {
1715
+ throw new Error(
1716
+ `todesktop.json invalid.
1717
+
1718
+ The "productName" property is no longer supported in todesktop.json. Please remove it and add it to your app's package.json instead.
1719
+
1720
+ We made this change because Electron also uses the "productName" if it exists in your app's package.json. If you do not add it to your package.json, your app name will default to the value of the "name" property in your package.json.`
1721
+ );
1722
+ }
1723
+ };
1724
+
1725
+ // src/utilities/projectConfig/computeFullProjectConfig.ts
1726
+ var import_path4 = require("path");
1727
+ var import_lodash2 = __toESM(require("lodash.merge"));
1728
+ var computeFullProjectConfig = (partialConfig, projectRoot) => {
1729
+ if (!partialConfig.extends) {
1730
+ logger_default.debug("No extends field, returning partial config");
1731
+ return partialConfig;
1732
+ } else {
1733
+ logger_default.debug("Extends field found, resolving");
1734
+ const parentConfigPath = (0, import_path4.resolve)(projectRoot, partialConfig.extends);
1735
+ const parentConfig = loadConfig_default(parentConfigPath);
1736
+ const parentFullConfig = computeFullProjectConfig(
1737
+ parentConfig,
1738
+ projectRoot
1739
+ );
1740
+ const result = (0, import_lodash2.default)({}, parentFullConfig, partialConfig);
1741
+ delete result.extends;
1742
+ return result;
1743
+ }
1744
+ };
1745
+ var computeFullProjectConfig_default = computeFullProjectConfig;
1746
+
1747
+ // src/utilities/projectConfig/getProjectConfig.ts
1748
+ var getProjectConfig_default = (configPath = null) => {
1749
+ if (configPath === null) {
1750
+ logger_default.debug("No config path provided, searching for one");
1751
+ configPath = import_find_up.default.sync("todesktop.json");
1752
+ if (!configPath) {
1753
+ throw new Error(
1754
+ "Can not find todesktop.json in this folder or any parent folders"
1755
+ );
1756
+ }
1757
+ } else {
1758
+ if (typeof configPath === "undefined" || configPath === "") {
1759
+ logger_default.error("Provided config path is empty");
1760
+ throw new Error("No config path provided");
1761
+ }
1762
+ configPath = (0, import_path5.resolve)(process.cwd(), configPath);
1763
+ if (!(0, import_fs.existsSync)(configPath)) {
1764
+ logger_default.error("Provided config path does not exist");
1765
+ throw new Error(`Config file not found at ${configPath}`);
1766
+ }
1767
+ }
1768
+ const projectRoot = (0, import_path5.dirname)(configPath);
1769
+ const partialConfig = loadConfig_default(configPath);
1770
+ partialConfig.appPath = partialConfig.appPath || ".";
1771
+ const config2 = computeFullProjectConfig_default(partialConfig, projectRoot);
1772
+ validateConfig_default({ config: config2, projectRoot });
1773
+ const result = resolveConfigPaths_default({ config: config2, projectRoot });
1774
+ return { config: result, unprocessedConfig: config2, projectRoot };
1775
+ };
1776
+
1777
+ // src/utilities/postToFirebaseFunction.ts
1778
+ var import_axios = __toESM(require("axios"));
1779
+ var { TODESKTOP_CLI_FIREBASE_FUNCTIONS_BASE } = getEnvironmentVariables_default();
1780
+ async function postToFirebaseFunction_default(functionName, body = {}, config2 = {}) {
1781
+ logger_default.debug({ functionName, body, config: config2 }, "postToFirebaseFunction");
1782
+ try {
1783
+ const response = await import_axios.default.post(
1784
+ `${TODESKTOP_CLI_FIREBASE_FUNCTIONS_BASE}${functionName}`,
1785
+ body,
1786
+ config2
1787
+ );
1788
+ logger_default.debug(
1789
+ { responseData: response.data },
1790
+ "postToFirebaseFunction: success"
1791
+ );
1792
+ return response.data;
1793
+ } catch (e) {
1794
+ logger_default.error({ error: e }, "postToFirebaseFunction: error");
1795
+ throw e;
1796
+ }
1797
+ }
1798
+
1799
+ // src/utilities/subscribeToFirebaseDoc.ts
1800
+ var subscribeToFirebaseDoc_default = async (key, receiver) => {
1801
+ const hasResolved = false;
1802
+ return new Promise((resolve4, reject) => {
1803
+ logger_default.debug({ key }, "subscribeToFirebaseDoc");
1804
+ firestore_default.doc(key).onSnapshot(
1805
+ (snapshot) => {
1806
+ receiver({
1807
+ snapshot,
1808
+ data: snapshot.exists ? snapshot.data() : void 0
1809
+ });
1810
+ if (!hasResolved) {
1811
+ resolve4();
1812
+ }
1813
+ },
1814
+ (err) => {
1815
+ reject(err);
1816
+ }
1817
+ );
1818
+ });
1819
+ };
1820
+
1821
+ // src/utilities/subscribeToBuild.ts
1822
+ var subscribeToBuild_default = async ({ appId, buildId, onDataReceived, userId }) => {
1823
+ try {
1824
+ return await subscribeToFirebaseDoc_default(
1825
+ `users/${userId}/applications/${appId}/builds/${buildId}`,
1826
+ async ({ data }) => {
1827
+ onDataReceived(data);
1828
+ }
1829
+ );
1830
+ } catch (e) {
1831
+ e.message = `Failed while subscribing to build; ${e.message}`;
1832
+ throw e;
1833
+ }
1834
+ };
1835
+
1836
+ // src/utilities/uploadApplicationSource.ts
1837
+ var import_fast_glob = __toESM(require("fast-glob"));
1838
+ var fs5 = __toESM(require("fs"));
1839
+ var path8 = __toESM(require("path"));
1840
+
1841
+ // src/utilities/generateS3Key.ts
1842
+ var generateS3Key_default = ({ appId, buildId, filenameSuffix }) => `${appId}/sourceArchives/${buildId}--${filenameSuffix}`;
1843
+
1844
+ // src/utilities/uploadToS3.ts
1845
+ var import_superagent = __toESM(require("superagent"));
1846
+ var import_util = __toESM(require("util"));
1847
+ var import_stream_to_array = __toESM(require("stream-to-array"));
1848
+ var { TODESKTOP_CLI_S3_BUCKET } = getEnvironmentVariables_default();
1849
+ var uploadToS3_default = async ({
1850
+ bucket = TODESKTOP_CLI_S3_BUCKET,
1851
+ inputStream,
1852
+ key,
1853
+ onProgress = () => {
1854
+ }
1855
+ }) => {
1856
+ const { data } = await getCallableFirebaseFunction_default("getSignedURL")({
1857
+ key
1858
+ });
1859
+ const body = await new Promise((resolve4) => {
1860
+ (0, import_stream_to_array.default)(inputStream).then(function(parts) {
1861
+ const buffers = parts.map(
1862
+ (part) => import_util.default.isBuffer(part) ? part : Buffer.from(part)
1863
+ );
1864
+ return resolve4(Buffer.concat(buffers));
1865
+ });
1866
+ });
1867
+ return new Promise((resolve4) => {
1868
+ import_superagent.default.put(data.signedURL).send(body).set("content-type", "application/zip").on("progress", (event) => onProgress(event)).end(() => {
1869
+ logger_default.debug({ bucket, key, url: data.uploadURL }, "uploadToS3");
1870
+ resolve4({ bucket, key, url: data.uploadURL });
1871
+ });
1872
+ return { bucket, key, url: data.uploadURL };
1873
+ });
1874
+ };
1875
+
1876
+ // src/utilities/zip.ts
1877
+ var import_archiver = __toESM(require("archiver"));
1878
+ var import_du = __toESM(require("du"));
1879
+ var import_fs2 = __toESM(require("fs"));
1880
+ var import_chalk = __toESM(require("chalk"));
1881
+ var import_path6 = __toESM(require("path"));
1882
+ async function zip_default({
1883
+ files,
1884
+ fileSizeLimit = 20,
1885
+ onError,
1886
+ onProgress,
1887
+ appPkgJson
1888
+ }) {
1889
+ logger_default.debug({ files }, "zip");
1890
+ const stream = (0, import_archiver.default)("zip");
1891
+ stream.on("warning", function(err) {
1892
+ if (err.code === "ENOENT") {
1893
+ console.warn(err);
1894
+ logger_default.warn({ err }, "zip: stream warning ENOENT");
1895
+ } else {
1896
+ onError(err);
1897
+ }
1898
+ });
1899
+ stream.on("error", function(err) {
1900
+ onError(err);
1901
+ });
1902
+ const processedFiles = await Promise.all(
1903
+ files.map(async (file) => {
1904
+ const stats = import_fs2.default.lstatSync(file.from);
1905
+ const isDirectory = stats.isDirectory();
1906
+ return {
1907
+ ...file,
1908
+ isDirectory,
1909
+ stats,
1910
+ size: isDirectory ? await (0, import_du.default)(file.from) : stats.size
1911
+ };
1912
+ })
1913
+ );
1914
+ const totalFileSize = processedFiles.reduce((sum, b) => sum + b.size, 0);
1915
+ if (totalFileSize > fileSizeLimit * 1e6) {
1916
+ onError(
1917
+ new Error(
1918
+ [
1919
+ `
1920
+
1921
+ Your app is larger than ${fileSizeLimit}MB. Your app is ${import_chalk.default.bold.red.underline(
1922
+ (totalFileSize / 1e6).toFixed(1) + "MB"
1923
+ )}.
1924
+ `,
1925
+ `You may be including unnecessary files in your app(see the \`appPath\` and \`appFiles\` options).`,
1926
+ `If not, this limit can be raised by setting the \`uploadSizeLimit\` option.`,
1927
+ `Learn more at https://www.npmjs.com/package/@todesktop/cli#uploadsizelimit---optional-number`
1928
+ ].join("\n")
1929
+ )
1930
+ );
1931
+ return stream;
1932
+ }
1933
+ processedFiles.forEach(({ from, isDirectory, stats, to }) => {
1934
+ if (isDirectory) {
1935
+ stream.directory(from, to);
1936
+ } else if (appPkgJson && to === import_path6.default.join("app", "package.json")) {
1937
+ stream.append(JSON.stringify(appPkgJson), {
1938
+ name: to
1939
+ });
1940
+ } else {
1941
+ stream.file(from, { name: to, mode: stats.mode });
1942
+ }
1943
+ });
1944
+ stream.finalize();
1945
+ stream.on(
1946
+ "progress",
1947
+ (progress) => onProgress({
1948
+ ...progress,
1949
+ percentage: progress.entries.processed / progress.entries.total * 100
1950
+ })
1951
+ );
1952
+ return stream;
1953
+ }
1954
+
1955
+ // src/utilities/uploadApplicationSource.ts
1956
+ var getAppFiles = async (globsInput, appPath, appPkgJson) => {
1957
+ const globs = ["!node_modules", "!**/node_modules", "!.git", "!**/.git"];
1958
+ if (globsInput && globsInput.length) {
1959
+ globs.push(
1960
+ ...globsInput,
1961
+ path8.join(appPath, "package.json"),
1962
+ path8.join(appPath, "package-lock.json"),
1963
+ path8.join(appPath, "yarn.lock"),
1964
+ path8.join(appPath, "pnpm-lock.yaml"),
1965
+ path8.join(appPath, "shrinkwrap.yaml")
1966
+ );
1967
+ } else {
1968
+ globs.push("**");
1969
+ }
1970
+ for (const hookName of ["todesktop:beforeInstall", "todesktop:afterPack"]) {
1971
+ if (appPkgJson.scripts && appPkgJson.scripts[hookName]) {
1972
+ globs.push(path8.join(appPath, appPkgJson.scripts[hookName]));
1973
+ }
1974
+ }
1975
+ const normalizedGlobs = globs.map((glob) => {
1976
+ const globToUse = path8.isAbsolute(glob) ? path8.relative(appPath, glob) : glob;
1977
+ return globToUse.replace(/\\/g, "/").replace(/\/+$/, "");
1978
+ }).filter((glob) => !glob.startsWith("..") && !glob.startsWith("!.."));
1979
+ let absolutePaths = await (0, import_fast_glob.default)(normalizedGlobs, {
1980
+ absolute: true,
1981
+ cwd: appPath,
1982
+ dot: true,
1983
+ followSymbolicLinks: false,
1984
+ onlyDirectories: false,
1985
+ onlyFiles: true,
1986
+ globstar: true,
1987
+ unique: true
1988
+ });
1989
+ if (process.platform === "win32") {
1990
+ absolutePaths = absolutePaths.map(
1991
+ (absolutePath) => absolutePath.replace(/\//g, path8.sep)
1992
+ );
1993
+ }
1994
+ if (!absolutePaths || !absolutePaths.length) {
1995
+ throw new Error("No files found to upload");
1996
+ } else if (!absolutePaths.filter((absolutePath) => absolutePath.endsWith(".js")).length) {
1997
+ throw new Error(
1998
+ `No .js files found to upload (${absolutePaths[0]}). There's likely an issue with the appFiles option. Learn more at https://www.npmjs.com/package/@todesktop/cli#appfiles----optional-array-of-glob-patterns. If this is not the case, please contact us.`
1999
+ );
2000
+ } else {
2001
+ let mainFilePath = appPath;
2002
+ if (appPkgJson.main) {
2003
+ mainFilePath = path8.join(mainFilePath, appPkgJson.main);
2004
+ }
2005
+ if (fs5.statSync(mainFilePath).isDirectory()) {
2006
+ mainFilePath = path8.join(mainFilePath, "index.js");
2007
+ }
2008
+ if (!absolutePaths.includes(mainFilePath)) {
2009
+ throw new Error(
2010
+ `The "main" file specified in your package.json (${appPkgJson.main ? path8.relative(appPath, mainFilePath) : "defaults to index.js"}) is not set to be uploaded to our servers. This is likely due to how you have configured the \`appFiles\` option. Learn more at https://www.npmjs.com/package/@todesktop/cli#appfiles----optional-array-of-glob-patterns. If this is not the case, please contact us.`
2011
+ );
2012
+ }
2013
+ }
2014
+ return absolutePaths.map((absolutePath) => {
2015
+ return {
2016
+ from: absolutePath,
2017
+ to: path8.join("app", path8.relative(appPath, absolutePath))
2018
+ };
2019
+ });
2020
+ };
2021
+ var uploadApplicationSource_default = async ({ appId, appPkgJson, buildId, config: config2, onProgress }) => {
2022
+ logger_default.debug(
2023
+ {
2024
+ appId,
2025
+ appPkgJson,
2026
+ buildId,
2027
+ config: config2,
2028
+ onProgress
2029
+ },
2030
+ "uploadApplicationSource"
2031
+ );
2032
+ let totalBytes = 0;
2033
+ const files = [
2034
+ ...await getAppFiles(config2.appFiles, config2.appPath, appPkgJson),
2035
+ ...(config2.extraContentFiles || []).map(({ from, to = "." }) => {
2036
+ return {
2037
+ from,
2038
+ to: path8.join("extraContentFiles", to, path8.basename(from))
2039
+ };
2040
+ }),
2041
+ ...(config2.extraResources || []).map(({ from, to = "." }) => {
2042
+ return {
2043
+ from,
2044
+ to: path8.join("extraResources", to, path8.basename(from))
2045
+ };
2046
+ }),
2047
+ {
2048
+ from: config2.icon,
2049
+ to: path8.join("icons", "appIcon" + path8.extname(config2.icon))
2050
+ }
2051
+ ];
2052
+ if (config2.linux) {
2053
+ if (config2.linux.icon) {
2054
+ files.push({
2055
+ from: config2.linux.icon,
2056
+ to: path8.join(
2057
+ "icons",
2058
+ "appIcon-linux" + path8.extname(config2.linux.icon)
2059
+ )
2060
+ });
2061
+ }
2062
+ }
2063
+ if (config2.mac) {
2064
+ if (config2.mac.entitlements) {
2065
+ files.push({
2066
+ from: config2.mac.entitlements,
2067
+ to: path8.join("other", "mac", "entitlements.mac.plist")
2068
+ });
2069
+ }
2070
+ if (config2.mac.icon) {
2071
+ files.push({
2072
+ from: config2.mac.icon,
2073
+ to: path8.join("icons", "appIcon-mac" + path8.extname(config2.mac.icon))
2074
+ });
2075
+ }
2076
+ }
2077
+ if (config2.dmg) {
2078
+ if (config2.dmg.background) {
2079
+ files.push({
2080
+ from: config2.dmg.background,
2081
+ to: path8.join("other", "mac", "dmg-background.tiff")
2082
+ });
2083
+ }
2084
+ }
2085
+ if (config2.windows) {
2086
+ if (config2.windows.icon) {
2087
+ files.push({
2088
+ from: config2.windows.icon,
2089
+ to: path8.join(
2090
+ "icons",
2091
+ "appIcon-windows" + path8.extname(config2.windows.icon)
2092
+ )
2093
+ });
2094
+ }
2095
+ }
2096
+ return uploadToS3_default({
2097
+ bucket: config2.bucket || void 0,
2098
+ inputStream: await zip_default({
2099
+ files,
2100
+ fileSizeLimit: config2.uploadSizeLimit,
2101
+ onError: (e) => {
2102
+ throw e;
2103
+ },
2104
+ onProgress({ fs: fs6 }) {
2105
+ totalBytes = fs6.totalBytes;
2106
+ },
2107
+ appPkgJson
2108
+ }),
2109
+ key: generateS3Key_default({
2110
+ appId,
2111
+ buildId,
2112
+ filenameSuffix: `${appPkgJson.name}.zip`
2113
+ }),
2114
+ onProgress({ loaded, total }) {
2115
+ onProgress(loaded / total * 100, loaded);
2116
+ }
2117
+ });
2118
+ };
2119
+
2120
+ // src/utilities/getPackageJson.ts
2121
+ var import_lodash3 = __toESM(require("lodash.merge"));
2122
+ var import_path7 = __toESM(require("path"));
2123
+ function deleteNullDeps(dep) {
2124
+ Object.keys(dep).forEach((key) => {
2125
+ if (dep[key] === null) {
2126
+ delete dep[key];
2127
+ }
2128
+ });
2129
+ return dep;
2130
+ }
2131
+ function removeNullDependencies(pkgJson) {
2132
+ const { dependencies, devDependencies } = pkgJson;
2133
+ if (dependencies) {
2134
+ pkgJson.dependencies = deleteNullDeps(dependencies);
2135
+ }
2136
+ if (devDependencies) {
2137
+ pkgJson.devDependencies = deleteNullDeps(devDependencies);
2138
+ }
2139
+ return pkgJson;
2140
+ }
2141
+ function getAppPkgJson({ config: config2 }) {
2142
+ const packageJsonFromConfig = config2.packageJson || {};
2143
+ const extendsFrom = packageJsonFromConfig.extends || "package.json";
2144
+ const packageJsonFromFile = readJson_default(import_path7.default.join(config2.appPath, extendsFrom));
2145
+ return removeNullDependencies(
2146
+ (0, import_lodash3.default)({}, packageJsonFromFile, packageJsonFromConfig)
2147
+ );
2148
+ }
2149
+ var getPackageJson_default = getAppPkgJson;
2150
+
2151
+ // src/utilities/runBuild.ts
2152
+ var runBuild_default = async ({ onEvent, shouldCodeSign = true, configPath }) => {
2153
+ logForCI_default("Getting application information...");
2154
+ const primaryUserId = currentUser().uid;
2155
+ const { config: config2, unprocessedConfig } = getProjectConfig_default(configPath);
2156
+ const appId = config2.id;
2157
+ const appPkgJson = getPackageJson_default({ config: config2 });
2158
+ onEvent("progress", {
2159
+ appId,
2160
+ appPkg: appPkgJson,
2161
+ preparationProgress: 0.02
2162
+ });
2163
+ logForCI_default("Preparing...");
2164
+ let buildId;
2165
+ try {
2166
+ const prepareResult = await postToFirebaseFunction_default("prepareNewBuild", {
2167
+ appPkgName: appPkgJson.name,
2168
+ appPkgProductName: appPkgJson.productName,
2169
+ appVersion: appPkgJson.version,
2170
+ id: config2.id,
2171
+ projectConfig: unprocessedConfig,
2172
+ shouldCodeSign: shouldCodeSign !== false,
2173
+ shouldRelease: false,
2174
+ userId: primaryUserId,
2175
+ versionControlInfo: await getVersionControlInfo_default(config2.appPath)
2176
+ });
2177
+ buildId = prepareResult.appData.meta.currentBuildProgress.id;
2178
+ const firebaseUnsubscribe = await subscribeToBuild_default({
2179
+ appId,
2180
+ buildId,
2181
+ onDataReceived: (data) => onEvent("progress", { build: data }),
2182
+ userId: prepareResult.userId
2183
+ });
2184
+ onEvent("firebase-subscribed", { firebaseUnsubscribe });
2185
+ } catch (e) {
2186
+ e.message = `Failed while preparing new build; ${e.message}`;
2187
+ throw e;
2188
+ }
2189
+ onEvent("progress", {
2190
+ preparationProgress: 0.05,
2191
+ preparationStageLabel: "Uploading"
2192
+ });
2193
+ logForCI_default("Uploading...");
2194
+ let sourceArchiveDetails;
2195
+ try {
2196
+ const uploadAppSourceResult = await uploadApplicationSource_default({
2197
+ appId,
2198
+ appPkgJson,
2199
+ buildId,
2200
+ config: config2,
2201
+ onProgress(progress, uploadedSize) {
2202
+ const dataToEmit = {
2203
+ preparationProgress: 0.05 + progress / 100 * 0.95,
2204
+ preparationUploadedSize: ""
2205
+ };
2206
+ if (uploadedSize) {
2207
+ dataToEmit.preparationUploadedSize = (0, import_pretty_bytes.default)(uploadedSize);
2208
+ }
2209
+ onEvent("progress", dataToEmit);
2210
+ }
2211
+ });
2212
+ sourceArchiveDetails = uploadAppSourceResult;
2213
+ } catch (e) {
2214
+ e.message = `Failed while uploading application source; ${e.message}`;
2215
+ throw e;
2216
+ }
2217
+ onEvent("progress", { isPreparationComplete: true });
2218
+ logForCI_default("Kicking off build...");
2219
+ try {
2220
+ await postToFirebaseFunction_default("kickOffBuild", {
2221
+ appId,
2222
+ appPkgName: appPkgJson.name,
2223
+ appVersion: appPkgJson.version,
2224
+ buildId,
2225
+ userId: primaryUserId,
2226
+ sourceArchiveDetails
2227
+ });
2228
+ } catch (e) {
2229
+ e.message = `Failed while kicking off build; ${e.message}`;
2230
+ throw e;
2231
+ }
2232
+ };
2233
+
2234
+ // src/utilities/shouldExitOnBuildFailure.ts
2235
+ var shouldExitOnBuildFailure_default = (build) => {
2236
+ const hasBuildSettled = ["linux", "mac", "windows"].every(
2237
+ (platform) => build[platform].shouldSkip || "succeeded" === build[platform].status || "failed" === build[platform].status && build[platform].numberOfAttemptedBuilds === 2
2238
+ );
2239
+ const hasSettledWithError = ["linux", "mac", "windows"].some(
2240
+ (platform) => "failed" === build[platform].status
2241
+ );
2242
+ return hasBuildSettled && hasSettledWithError;
2243
+ };
2244
+
2245
+ // src/components/Build.tsx
2246
+ var import_jsx_runtime13 = require("react/jsx-runtime");
2247
+ var initalState = {
2248
+ appId: null,
2249
+ appPkg: null,
2250
+ build: null,
2251
+ error: null,
2252
+ hasBuildEverFailed: false,
2253
+ isPreparationComplete: false,
2254
+ preparationStageLabel: "Preparing",
2255
+ preparationProgress: 0,
2256
+ preparationUploadedSize: null
2257
+ };
2258
+ var Build = ({ commandUsed, shouldCodeSign = true, configPath }) => {
2259
+ const exit = useExit_default();
2260
+ const [
2261
+ {
2262
+ appId,
2263
+ appPkg,
2264
+ build,
2265
+ error,
2266
+ hasBuildEverFailed,
2267
+ isPreparationComplete,
2268
+ preparationStageLabel,
2269
+ preparationProgress,
2270
+ preparationUploadedSize
2271
+ },
2272
+ setState
2273
+ ] = (0, import_react6.useState)(initalState);
2274
+ const onError = (e) => {
2275
+ const error2 = e.response ? e.response.data : e;
2276
+ logForCI_default(error2);
2277
+ setState((prevState) => ({
2278
+ ...prevState,
2279
+ error: error2
2280
+ }));
2281
+ };
2282
+ (0, import_react6.useEffect)(() => {
2283
+ let firebaseUnsubscribe;
2284
+ runBuild_default({
2285
+ onEvent: (eventName, data) => {
2286
+ logger_default.debug({ eventName, data }, "Build component: runBuild.onEvent");
2287
+ if (eventName === "firebase-subscribed") {
2288
+ firebaseUnsubscribe = data.firebaseUnsubscribe;
2289
+ } else if (eventName === "progress") {
2290
+ setState((prevState) => ({ ...prevState, ...data }));
2291
+ }
2292
+ },
2293
+ shouldCodeSign,
2294
+ configPath
2295
+ }).catch(onError);
2296
+ return () => {
2297
+ if (firebaseUnsubscribe) {
2298
+ firebaseUnsubscribe();
2299
+ }
2300
+ };
2301
+ }, [commandUsed, shouldCodeSign, configPath]);
2302
+ (0, import_react6.useEffect)(() => {
2303
+ if (hasBuildEverFailed && shouldExitOnBuildFailure_default(build)) {
2304
+ setTimeout(() => exit(new Error("Build has failed")), 10);
2305
+ }
2306
+ }, [build, exit, hasBuildEverFailed]);
2307
+ if (error) {
2308
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ErrorDisplay_default, { commandUsed, error });
2309
+ }
2310
+ if (!appPkg) {
2311
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(InitialLoadingState_default, {});
2312
+ }
2313
+ const onBuildFailure = () => {
2314
+ if (hasBuildEverFailed) {
2315
+ return;
2316
+ }
2317
+ setState((prevState) => ({
2318
+ ...prevState,
2319
+ hasBuildEverFailed: true
2320
+ }));
2321
+ };
2322
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2323
+ MainLayout_default,
2324
+ {
2325
+ appId,
2326
+ appName: build && build.appName || appPkg.name,
2327
+ appVersion: build && build.appVersion || appPkg.version,
2328
+ build,
2329
+ commandUsed,
2330
+ hasBuildEverFailed,
2331
+ children: isPreparationComplete ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(BuildProgress_default, { build, onBuildFailure }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2332
+ Preparation_default,
2333
+ {
2334
+ progressPercentage: preparationProgress,
2335
+ stageLabel: preparationStageLabel,
2336
+ uploadedSize: preparationUploadedSize
2337
+ }
2338
+ )
2339
+ }
2340
+ );
2341
+ };
2342
+ Build.propTypes = {
2343
+ commandUsed: import_prop_types9.default.string.isRequired,
2344
+ shouldCodeSign: import_prop_types9.default.bool,
2345
+ configPath: import_prop_types9.default.string
2346
+ };
2347
+ var Build_default = Build;
2348
+
2349
+ // src/utilities/checkIfReactIsUsable.ts
2350
+ var import_react7 = require("react");
2351
+ var checkIfReactIsUsable_default = ({ cons = console, proc = process } = {}) => {
2352
+ try {
2353
+ (0, import_react7.useState)(null);
2354
+ } catch (e) {
2355
+ cons.error(e);
2356
+ proc.exit(1);
2357
+ }
2358
+ };
2359
+
2360
+ // src/components/LoginHOC.tsx
2361
+ var import_ink15 = require("ink");
2362
+ var import_react10 = require("react");
2363
+ var import_prop_types11 = __toESM(require("prop-types"));
2364
+ var import_is_ci4 = __toESM(require("is-ci"));
2365
+
2366
+ // src/components/Login.tsx
2367
+ var import_ink14 = require("ink");
2368
+ var import_prop_types10 = __toESM(require("prop-types"));
2369
+ var import_react9 = require("react");
2370
+ var import_react_final_form = require("react-final-form");
2371
+
2372
+ // src/components/TextInput.tsx
2373
+ var import_ink_text_input = __toESM(require("ink-text-input"));
2374
+ var import_react8 = __toESM(require("react"));
2375
+ var import_jsx_runtime14 = require("react/jsx-runtime");
2376
+ var TextInput = ({ onBlur, onFocus, ...props }) => {
2377
+ import_react8.default.useEffect(() => {
2378
+ onFocus();
2379
+ return onBlur;
2380
+ }, [onFocus, onBlur]);
2381
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ink_text_input.default, { ...props, showCursor: true });
2382
+ };
2383
+ var TextInput_default = TextInput;
2384
+
2385
+ // src/components/Login.tsx
2386
+ var import_jsx_runtime15 = require("react/jsx-runtime");
2387
+ var loginFields = [
2388
+ {
2389
+ name: "email",
2390
+ label: "Your email",
2391
+ placeholder: "name@email.com",
2392
+ Input: TextInput_default,
2393
+ validate: (value) => {
2394
+ if (!value)
2395
+ return "Required";
2396
+ }
2397
+ },
2398
+ {
2399
+ name: "accessToken",
2400
+ label: "Your access token",
2401
+ placeholder: "********",
2402
+ Input: TextInput_default,
2403
+ validate: (value) => {
2404
+ if (!value)
2405
+ return "Required";
2406
+ },
2407
+ mask: "*"
2408
+ }
2409
+ ];
2410
+ var Login = ({ setIsLoggedIn }) => {
2411
+ const [activeField, setActiveField] = (0, import_react9.useState)(0);
2412
+ const [error, setError] = (0, import_react9.useState)(null);
2413
+ const [failureMessage, setFailureMessage] = (0, import_react9.useState)(null);
2414
+ const [isSubmittingForm, setIsSubmittingForm] = (0, import_react9.useState)(false);
2415
+ const onFailure = (message2) => {
2416
+ setIsSubmittingForm(false);
2417
+ setFailureMessage(message2);
2418
+ setActiveField(0);
2419
+ };
2420
+ const onSubmitLogin = async ({ email, accessToken }) => {
2421
+ setFailureMessage(null);
2422
+ setIsSubmittingForm(true);
2423
+ try {
2424
+ const jwtToken = await postToFirebaseFunction_default("loginWithAccessToken", {
2425
+ email,
2426
+ accessToken
2427
+ });
2428
+ if (jwtToken) {
2429
+ await signInWithCustomToken(jwtToken);
2430
+ setAuthConfig(email, accessToken, jwtToken);
2431
+ setIsLoggedIn(true);
2432
+ } else {
2433
+ onFailure("Incorrrect credentials. Try again");
2434
+ }
2435
+ } catch (err) {
2436
+ if (err.code === 500) {
2437
+ setError(err);
2438
+ } else {
2439
+ onFailure(`Login unsuccessful: ${err.message}`);
2440
+ }
2441
+ }
2442
+ };
2443
+ if (error)
2444
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ErrorDisplay_default, { error });
2445
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
2446
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink14.Text, { children: "You are not currently logged in." }),
2447
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink14.Text, { children: "Log in below:" }),
2448
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink14.Box, { marginBottom: 1 }),
2449
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_final_form.Form, { onSubmit: onSubmitLogin, children: ({ handleSubmit, validating }) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink14.Box, { flexDirection: "column", children: loginFields.map(
2450
+ ({ name, label, placeholder, validate, Input, mask }, index) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_final_form.Field, { name, validate, children: ({ input, meta }) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink14.Box, { flexDirection: "column", children: activeField >= index ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_ink14.Box, { children: [
2451
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_ink14.Text, { bold: activeField === index, children: [
2452
+ label,
2453
+ ": "
2454
+ ] }),
2455
+ activeField === index ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2456
+ Input,
2457
+ {
2458
+ ...input,
2459
+ mask,
2460
+ placeholder,
2461
+ onSubmit: () => {
2462
+ if (meta.valid && !validating) {
2463
+ setActiveField((value) => value + 1);
2464
+ if (activeField === loginFields.length - 1) {
2465
+ handleSubmit();
2466
+ }
2467
+ } else {
2468
+ input.onBlur();
2469
+ }
2470
+ }
2471
+ }
2472
+ ) : input.value && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink14.Text, { children: name === "accessToken" ? input.value.replace(/./gi, "*") : input.value }) || placeholder && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink14.Text, { color: "gray", children: placeholder }),
2473
+ meta.invalid && meta.touched && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink14.Box, { marginLeft: 2, marginRight: 1, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink14.Text, { color: "red", children: "\u2716" }) }),
2474
+ meta.error && meta.touched && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Error2, { errorMessage: meta.error })
2475
+ ] }) : null }) }, name)
2476
+ ) }) }),
2477
+ isSubmittingForm && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink14.Box, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink14.Text, { children: "Logging in..." }) }),
2478
+ failureMessage && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Error2, { errorMessage: failureMessage, marginTop: true })
2479
+ ] });
2480
+ };
2481
+ var Error2 = ({ errorMessage, marginTop }) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink14.Box, { marginTop: marginTop ? 1 : 0, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink14.Text, { color: "red", children: errorMessage }) });
2482
+ Error2.propTypes = {
2483
+ errorMessage: import_prop_types10.default.string.isRequired,
2484
+ marginTop: import_prop_types10.default.bool
2485
+ };
2486
+ Login.propTypes = {
2487
+ setIsLoggedIn: import_prop_types10.default.func.isRequired
2488
+ };
2489
+ var Login_default = Login;
2490
+
2491
+ // src/components/LoginHOC.tsx
2492
+ var import_jsx_runtime16 = require("react/jsx-runtime");
2493
+ var LoginHOC = ({ children, isInteractive = true }) => {
2494
+ const [isLoggedIn, setIsLoggedIn] = (0, import_react10.useState)(false);
2495
+ const [isEffectDone, setEffectDone] = (0, import_react10.useState)(false);
2496
+ const [error, setError] = (0, import_react10.useState)(null);
2497
+ const { isRawModeSupported } = (0, import_ink15.useStdin)();
2498
+ const onFailure = (message2, err = {}) => {
2499
+ setError({
2500
+ ...err,
2501
+ message: message2
2502
+ });
2503
+ };
2504
+ (0, import_react10.useEffect)(() => {
2505
+ async function isAccessTokenValid() {
2506
+ const { accessToken, email, jwtToken } = getAuthConfig();
2507
+ let userCreds = null;
2508
+ const { TODESKTOP_ACCESS_TOKEN, TODESKTOP_EMAIL } = getEnvironmentVariables_default();
2509
+ try {
2510
+ if (TODESKTOP_ACCESS_TOKEN && TODESKTOP_EMAIL) {
2511
+ const newJwtToken = await postToFirebaseFunction_default(
2512
+ "loginWithAccessToken",
2513
+ {
2514
+ email: TODESKTOP_EMAIL,
2515
+ accessToken: TODESKTOP_ACCESS_TOKEN
2516
+ }
2517
+ );
2518
+ if (newJwtToken) {
2519
+ userCreds = await signInWithCustomToken(newJwtToken);
2520
+ if (userCreds.user)
2521
+ setIsLoggedIn(true);
2522
+ }
2523
+ } else if (!isRawModeSupported || import_is_ci4.default) {
2524
+ onFailure(
2525
+ "ToDesktop environment variables not found. When automating builds with your CI provider, you need to specify environment variables for TODESKTOP_EMAIL and TODESKTOP_ACCESS_TOKEN."
2526
+ );
2527
+ }
2528
+ } catch (err) {
2529
+ onFailure("Log in with environment variables failed.", err);
2530
+ }
2531
+ try {
2532
+ if (!userCreds && jwtToken) {
2533
+ userCreds = await signInWithCustomToken(jwtToken);
2534
+ if (userCreds.user)
2535
+ setIsLoggedIn(true);
2536
+ }
2537
+ } catch (err) {
2538
+ }
2539
+ try {
2540
+ if (!userCreds && accessToken && email) {
2541
+ const newJwtToken = await postToFirebaseFunction_default(
2542
+ "loginWithAccessToken",
2543
+ { email, accessToken }
2544
+ );
2545
+ if (newJwtToken) {
2546
+ userCreds = await signInWithCustomToken(newJwtToken);
2547
+ setAuthConfig(email, accessToken, newJwtToken);
2548
+ if (userCreds.user)
2549
+ setIsLoggedIn(true);
2550
+ }
2551
+ }
2552
+ } catch (err) {
2553
+ }
2554
+ setEffectDone(true);
2555
+ }
2556
+ isAccessTokenValid();
2557
+ }, []);
2558
+ if (error && isInteractive) {
2559
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ErrorDisplay_default, { error });
2560
+ }
2561
+ if (!isEffectDone) {
2562
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_ink15.Text, { children: "..." });
2563
+ }
2564
+ if (!isLoggedIn && isInteractive) {
2565
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Login_default, { setIsLoggedIn });
2566
+ }
2567
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_jsx_runtime16.Fragment, { children });
2568
+ };
2569
+ LoginHOC.propTypes = {
2570
+ children: import_prop_types11.default.object,
2571
+ isInteractive: import_prop_types11.default.bool
2572
+ };
2573
+ var LoginHOC_default = LoginHOC;
2574
+
2575
+ // src/components/ErrorBoundary.tsx
2576
+ var import_react11 = __toESM(require("react"));
2577
+ var Sentry2 = __toESM(require("@sentry/node"));
2578
+ var ErrorBoundary = class extends import_react11.default.Component {
2579
+ constructor(props) {
2580
+ super(props);
2581
+ this.state = { eventId: null, error: null };
2582
+ }
2583
+ static getDerivedStateFromError(error) {
2584
+ return { error };
2585
+ }
2586
+ componentDidCatch(error, errorInfo) {
2587
+ Sentry2.withScope((scope) => {
2588
+ scope.setExtras(errorInfo);
2589
+ const eventId = Sentry2.captureException(error);
2590
+ this.setState({ eventId });
2591
+ });
2592
+ }
2593
+ render() {
2594
+ if (this.state.error) {
2595
+ throw this.state.error;
2596
+ }
2597
+ return this.props.children;
2598
+ }
2599
+ };
2600
+ var ErrorBoundary_default = ErrorBoundary;
2601
+
2602
+ // src/components/OngoingBuildGuard.tsx
2603
+ var import_ink19 = require("ink");
2604
+ var import_ink_select_input = __toESM(require("ink-select-input"));
2605
+ var import_prop_types15 = __toESM(require("prop-types"));
2606
+ var import_react13 = require("react");
2607
+
2608
+ // src/components/CustomSelectInputIndicator.tsx
2609
+ var import_ink16 = require("ink");
2610
+ var import_prop_types12 = __toESM(require("prop-types"));
2611
+ var import_jsx_runtime17 = require("react/jsx-runtime");
2612
+ var CustomSelectInputIndicator = ({ isSelected, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_ink16.Box, { marginRight: 1, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_ink16.Text, { children: isSelected ? process.platform === "win32" ? ">" : "\u276F" : " " }) });
2613
+ CustomSelectInputIndicator.propTypes = {
2614
+ isSelected: import_prop_types12.default.bool.isRequired
2615
+ };
2616
+ var CustomSelectInputIndicator_default = CustomSelectInputIndicator;
2617
+
2618
+ // src/components/CustomSelectInputItem.tsx
2619
+ var import_ink17 = require("ink");
2620
+ var import_prop_types13 = __toESM(require("prop-types"));
2621
+ var import_jsx_runtime18 = require("react/jsx-runtime");
2622
+ var CustomSelectInputItem = ({ isSelected, label, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_ink17.Text, { bold: isSelected, color: isSelected ? void 0 : "gray", ...props, children: label });
2623
+ CustomSelectInputItem.propTypes = {
2624
+ isSelected: import_prop_types13.default.bool.isRequired,
2625
+ label: import_prop_types13.default.string.isRequired
2626
+ };
2627
+ var CustomSelectInputItem_default = CustomSelectInputItem;
2628
+
2629
+ // src/components/ViewBuild.tsx
2630
+ var import_ink18 = require("ink");
2631
+ var import_prop_types14 = __toESM(require("prop-types"));
2632
+ var import_react12 = require("react");
2633
+
2634
+ // src/utilities/getLatestBuildId.ts
2635
+ var getLatestBuildId_default = async ({ appId, userId }) => {
2636
+ logger_default.debug({ appId }, "getLatestBuildId");
2637
+ const appRef = firestore_default.doc(`users/${userId}/applications/${appId}`);
2638
+ const appSnapshot = await appRef.get();
2639
+ if (!appSnapshot.exists) {
2640
+ throw new Error(`Application with ID of ${appId} doesn't exist.`);
2641
+ }
2642
+ const buildsResult = await appRef.collection("builds").orderBy("createdAt", "desc").limit(1).get();
2643
+ if (buildsResult.empty) {
2644
+ return null;
2645
+ } else {
2646
+ return buildsResult.docs[0].id;
2647
+ }
2648
+ };
2649
+
2650
+ // src/utilities/findAppUserId.ts
2651
+ var getCollection = (collectionRef) => {
2652
+ return collectionRef.get().then((resp) => {
2653
+ const result = [];
2654
+ resp.forEach((doc) => result.push(doc.data()));
2655
+ return result;
2656
+ });
2657
+ };
2658
+ var acceptedUsersCollection = (id) => firestore_default.collection(`users/${id}/acceptedUsers`);
2659
+ var findAppUserId = async (appId) => {
2660
+ const { uid, email } = currentUser();
2661
+ const acceptedUsers = await getCollection(acceptedUsersCollection(uid));
2662
+ const primaryUser = { id: uid, label: email };
2663
+ const users = [
2664
+ primaryUser,
2665
+ ...acceptedUsers.map((user) => ({ id: user.id, label: user.email }))
2666
+ ];
2667
+ for (const user of users) {
2668
+ try {
2669
+ const doc = await firestore_default.doc(`users/${user.id}/applications/${appId}`).get();
2670
+ if (doc.exists)
2671
+ return user;
2672
+ } catch (err) {
2673
+ }
2674
+ }
2675
+ return primaryUser;
2676
+ };
2677
+ var findAppUserId_default = findAppUserId;
2678
+
2679
+ // src/components/ViewBuild.tsx
2680
+ var import_jsx_runtime19 = require("react/jsx-runtime");
2681
+ var ViewBuild = ({ commandUsed, id, configPath }) => {
2682
+ const exit = useExit_default();
2683
+ const [
2684
+ {
2685
+ appId,
2686
+ arbitraryMessageComponent,
2687
+ build,
2688
+ error,
2689
+ hasBuildEverFailed,
2690
+ isLoading
2691
+ },
2692
+ setState
2693
+ ] = (0, import_react12.useState)({
2694
+ appId: null,
2695
+ arbitraryMessageComponent: null,
2696
+ build: null,
2697
+ error: null,
2698
+ hasBuildEverFailed: false,
2699
+ isLoading: true
2700
+ });
2701
+ const onError = (e) => {
2702
+ const error2 = e.response ? e.response.data : e;
2703
+ logForCI_default(error2);
2704
+ setState((prevState) => ({
2705
+ ...prevState,
2706
+ error: error2
2707
+ }));
2708
+ };
2709
+ (0, import_react12.useEffect)(() => {
2710
+ let firebaseUnsubscribe;
2711
+ async function viewBuild() {
2712
+ let config2;
2713
+ try {
2714
+ config2 = getProjectConfig_default(configPath).config;
2715
+ } catch (e) {
2716
+ setState((previousState) => ({ ...previousState, error: e }));
2717
+ return;
2718
+ }
2719
+ const { id: userId } = await findAppUserId_default(config2.id);
2720
+ const subscribe = (buildId) => {
2721
+ subscribeToBuild_default({
2722
+ appId: config2.id,
2723
+ buildId,
2724
+ onDataReceived: (data) => {
2725
+ if (!data) {
2726
+ onError(
2727
+ new Error(
2728
+ `No such build exists (${buildId}). Please contact us if this is an error`
2729
+ )
2730
+ );
2731
+ }
2732
+ setState((prevState) => ({
2733
+ ...prevState,
2734
+ appId: config2.id,
2735
+ build: data,
2736
+ isLoading: false
2737
+ }));
2738
+ },
2739
+ userId
2740
+ }).then((firebaseUnsubscribeResult) => {
2741
+ firebaseUnsubscribe = firebaseUnsubscribeResult;
2742
+ }).catch(onError);
2743
+ };
2744
+ if (id) {
2745
+ subscribe(id);
2746
+ } else {
2747
+ getLatestBuildId_default({ appId: config2.id, userId }).catch(onError).then((latestBuildId) => {
2748
+ if (!latestBuildId) {
2749
+ setState((previousState) => ({
2750
+ ...previousState,
2751
+ arbitraryMessageComponent: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_ink18.Text, { children: "There are no builds yet" })
2752
+ }));
2753
+ return;
2754
+ }
2755
+ return subscribe(latestBuildId);
2756
+ });
2757
+ }
2758
+ }
2759
+ viewBuild();
2760
+ return () => {
2761
+ if (firebaseUnsubscribe) {
2762
+ firebaseUnsubscribe();
2763
+ }
2764
+ };
2765
+ }, [id]);
2766
+ (0, import_react12.useEffect)(() => {
2767
+ if (hasBuildEverFailed && shouldExitOnBuildFailure_default(build)) {
2768
+ setTimeout(() => exit(new Error("Build has failed")), 10);
2769
+ }
2770
+ }, [build, exit, hasBuildEverFailed]);
2771
+ (0, import_react12.useEffect)(() => {
2772
+ if (arbitraryMessageComponent) {
2773
+ setTimeout(() => exit(new Error("Validation failed")), 10);
2774
+ }
2775
+ }, [arbitraryMessageComponent, exit]);
2776
+ if (error) {
2777
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ErrorDisplay_default, { commandUsed, error });
2778
+ }
2779
+ if (arbitraryMessageComponent) {
2780
+ return arbitraryMessageComponent;
2781
+ }
2782
+ if (isLoading) {
2783
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_ink18.Text, { children: "Loading..." });
2784
+ }
2785
+ const onBuildFailure = () => {
2786
+ if (hasBuildEverFailed) {
2787
+ return;
2788
+ }
2789
+ setState((prevState) => ({
2790
+ ...prevState,
2791
+ hasBuildEverFailed: true
2792
+ }));
2793
+ };
2794
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2795
+ MainLayout_default,
2796
+ {
2797
+ appId,
2798
+ appName: build.appName,
2799
+ appVersion: build.appVersion,
2800
+ build,
2801
+ commandUsed,
2802
+ hasBuildEverFailed,
2803
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(BuildProgress_default, { build, onBuildFailure })
2804
+ }
2805
+ );
2806
+ };
2807
+ ViewBuild.propTypes = {
2808
+ id: (props, propName, componentName) => {
2809
+ if ([props.id, props.shouldViewLatest].filter(Boolean).length !== 1) {
2810
+ return new Error(
2811
+ `Exactly one of 'id' and 'shouldViewLatest' must be specified in '${componentName}'`
2812
+ );
2813
+ }
2814
+ const type = typeof props.id;
2815
+ if (!["string", "undefined"].includes(type)) {
2816
+ return new Error(
2817
+ `'id' is a '${type}', not a string, in '${componentName}'.`
2818
+ );
2819
+ }
2820
+ },
2821
+ commandUsed: import_prop_types14.default.string.isRequired,
2822
+ shouldViewLatest: (props, propName, componentName) => {
2823
+ if ([props.id, props.shouldViewLatest].filter(Boolean).length !== 1) {
2824
+ return new Error(
2825
+ `Exactly one of 'id' and 'shouldViewLatest' must be specified in '${componentName}'`
2826
+ );
2827
+ }
2828
+ const type = typeof props.shouldViewLatest;
2829
+ if (!["boolean", "undefined"].includes(type)) {
2830
+ return new Error(
2831
+ `'shouldViewLatest' is a '${type}', not a boolean, in '${componentName}'.`
2832
+ );
2833
+ }
2834
+ },
2835
+ configPath: import_prop_types14.default.string
2836
+ };
2837
+ var ViewBuild_default = ViewBuild;
2838
+
2839
+ // src/components/OngoingBuildGuard.tsx
2840
+ var import_is_ci5 = __toESM(require("is-ci"));
2841
+ var import_jsx_runtime20 = require("react/jsx-runtime");
2842
+ var OngoingBuildGuard = ({ children, commandUsed, configPath }) => {
2843
+ const { isRawModeSupported } = (0, import_ink19.useStdin)();
2844
+ const onInput = useInput_default();
2845
+ const exit = useExit_default();
2846
+ const [{ appId, builds, error, isLoading, itemChosen }, setState] = (0, import_react13.useState)({
2847
+ appId: null,
2848
+ builds: [],
2849
+ error: null,
2850
+ isLoading: true,
2851
+ itemChosen: null
2852
+ });
2853
+ (0, import_react13.useEffect)(() => {
2854
+ if (!isLoading) {
2855
+ return;
2856
+ }
2857
+ (async () => {
2858
+ try {
2859
+ const applicationId = getProjectConfig_default(configPath).config.id;
2860
+ const { id } = await findAppUserId_default(applicationId);
2861
+ const buildsResult = await firestore_default.doc(`users/${id}/applications/${applicationId}`).collection("builds").orderBy("createdAt", "desc").limit(10).get();
2862
+ const stateUpdates = {
2863
+ appId: applicationId,
2864
+ isLoading: false,
2865
+ builds: []
2866
+ };
2867
+ if (!buildsResult.empty) {
2868
+ const latestBuild = buildsResult.docs[0].data();
2869
+ const isRunning = isBuildRunning(latestBuild) && latestBuild.status !== "preparation";
2870
+ logger_default.debug(
2871
+ { isRunning },
2872
+ "OngoingBuildGuard component: got latest build"
2873
+ );
2874
+ stateUpdates.builds = buildsResult.docs.map((doc) => doc.data()).filter((build) => {
2875
+ if (isBuildRunning(build) && build.status !== "preparation") {
2876
+ return true;
2877
+ }
2878
+ return false;
2879
+ });
2880
+ }
2881
+ setState((previousState) => ({
2882
+ ...previousState,
2883
+ ...stateUpdates
2884
+ }));
2885
+ } catch (e) {
2886
+ setState((previousState) => ({
2887
+ ...previousState,
2888
+ error: e
2889
+ }));
2890
+ }
2891
+ })();
2892
+ }, [builds, itemChosen, isLoading, isRawModeSupported]);
2893
+ if (isRawModeSupported) {
2894
+ onInput(() => {
2895
+ });
2896
+ }
2897
+ if (error) {
2898
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ErrorDisplay_default, { commandUsed, error });
2899
+ }
2900
+ if (isLoading) {
2901
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_ink19.Text, { children: "..." });
2902
+ }
2903
+ if (itemChosen) {
2904
+ logger_default.debug({ itemChosen }, "OngoingBuildGuard component: item chosen");
2905
+ const build = builds.find(Boolean);
2906
+ if (itemChosen.value === "view") {
2907
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ViewBuild_default, { commandUsed, id: build.id });
2908
+ } else if (itemChosen.value === "cancel") {
2909
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(CancelBuild_default, { appId, commandUsed, id: build.id, children });
2910
+ } else if (itemChosen.value === "concurrent") {
2911
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_jsx_runtime20.Fragment, { children });
2912
+ } else {
2913
+ setTimeout(exit, 10);
2914
+ }
2915
+ }
2916
+ if (builds.length && import_is_ci5.default === false && isRawModeSupported) {
2917
+ const latestBuild = builds.find(Boolean);
2918
+ const multiple = builds.length > 1;
2919
+ const items = [
2920
+ {
2921
+ label: `View ${multiple ? "latest" : "it"}`,
2922
+ value: "view"
2923
+ }
2924
+ ];
2925
+ if (builds.some((build) => isBuildCancellable(build))) {
2926
+ items.push({
2927
+ label: `Cancel ${multiple ? "latest" : "it"} and start a new build`,
2928
+ value: "cancel"
2929
+ });
2930
+ items.push({
2931
+ label: "Start a concurrent build",
2932
+ value: "concurrent"
2933
+ });
2934
+ }
2935
+ items.push({
2936
+ label: "Exit",
2937
+ value: "exit"
2938
+ });
2939
+ const handleSelect = (itemChosen2) => setState((previousState) => ({ ...previousState, itemChosen: itemChosen2 }));
2940
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
2941
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_ink19.Box, { marginBottom: 1, children: [
2942
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_ink19.Text, { bold: true, children: multiple ? "There are ongoing builds " : "There is an ongoing build " }),
2943
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_ink19.Text, { children: [
2944
+ "(",
2945
+ latestBuild.appName,
2946
+ latestBuild.appVersion ? ` v${latestBuild.appVersion}` : "",
2947
+ ")"
2948
+ ] })
2949
+ ] }),
2950
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2951
+ import_ink_select_input.default,
2952
+ {
2953
+ indicatorComponent: CustomSelectInputIndicator_default,
2954
+ itemComponent: CustomSelectInputItem_default,
2955
+ items,
2956
+ onSelect: handleSelect
2957
+ }
2958
+ )
2959
+ ] });
2960
+ } else {
2961
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_jsx_runtime20.Fragment, { children });
2962
+ }
2963
+ };
2964
+ OngoingBuildGuard.propTypes = {
2965
+ commandUsed: import_prop_types15.default.string.isRequired,
2966
+ children: import_prop_types15.default.oneOfType([import_prop_types15.default.array, import_prop_types15.default.object]),
2967
+ configPath: import_prop_types15.default.string
2968
+ };
2969
+ var OngoingBuildGuard_default = OngoingBuildGuard;
2970
+
2971
+ // src/utilities/useAnalytics.ts
2972
+ var import_react14 = require("react");
2973
+ var useAnalyticsCommand = (command, flags = {}, properties = {}) => {
2974
+ const [hasAttemptedTracking, setTrackingAttempt] = (0, import_react14.useState)(false);
2975
+ logger_default.info({ command, flags, properties }, "useAnalyticsCommand");
2976
+ try {
2977
+ if (flags.projectPath) {
2978
+ properties = {
2979
+ ...properties,
2980
+ ...getProjectConfig_default()
2981
+ };
2982
+ }
2983
+ } catch (err) {
2984
+ }
2985
+ (0, import_react14.useEffect)(() => {
2986
+ const authUnsubscribe = onUserAuth(async (user) => {
2987
+ if (user.uid) {
2988
+ identify(
2989
+ user.uid,
2990
+ {
2991
+ email: user.email,
2992
+ displayName: user.displayName
2993
+ },
2994
+ () => {
2995
+ track(
2996
+ ANALYTICS_EVENT.CLI_COMMAND,
2997
+ {
2998
+ ...properties,
2999
+ flags,
3000
+ command
3001
+ },
3002
+ () => setTrackingAttempt(true)
3003
+ );
3004
+ authUnsubscribe && authUnsubscribe();
3005
+ }
3006
+ );
3007
+ }
3008
+ });
3009
+ setTimeout(() => setTrackingAttempt(true), 5e3);
3010
+ }, []);
3011
+ return { hasAttemptedTracking };
3012
+ };
3013
+
3014
+ // src/commands/BuildCommand.tsx
3015
+ var import_jsx_runtime21 = require("react/jsx-runtime");
3016
+ var BuildCommand = ({
3017
+ shouldCodeSign = true,
3018
+ configPath = null
3019
+ }) => {
3020
+ checkIfReactIsUsable_default();
3021
+ useAnalyticsCommand("build", {
3022
+ codeSign: shouldCodeSign,
3023
+ config: configPath
3024
+ });
3025
+ const commandUsed = "todesktop build";
3026
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(LoginHOC_default, { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(OngoingBuildGuard_default, { configPath, commandUsed, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3027
+ Build_default,
3028
+ {
3029
+ commandUsed,
3030
+ shouldCodeSign,
3031
+ configPath
3032
+ }
3033
+ ) }) }) });
3034
+ };
3035
+ var BuildCommand_default = BuildCommand;
3036
+
3037
+ // src/components/ViewBuilds.tsx
3038
+ var import_ink25 = require("ink");
3039
+ var import_prop_types22 = __toESM(require("prop-types"));
3040
+ var import_react16 = require("react");
3041
+
3042
+ // src/components/Table.tsx
3043
+ var import_ink23 = require("ink");
3044
+ var import_prop_types20 = __toESM(require("prop-types"));
3045
+
3046
+ // src/components/TableEnd.tsx
3047
+ var import_ink20 = require("ink");
3048
+ var import_prop_types16 = __toESM(require("prop-types"));
3049
+ var import_jsx_runtime22 = require("react/jsx-runtime");
3050
+ var TableEnd = ({ keyDetails, ...props }) => {
3051
+ let content = "\u2514";
3052
+ content += Object.values(keyDetails).map(({ width }) => "\u2500".repeat(width + 2)).join("\u2534");
3053
+ content += "\u2518";
3054
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ink20.Box, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ink20.Text, { children: content }) });
3055
+ };
3056
+ TableEnd.propTypes = {
3057
+ keyDetails: import_prop_types16.default.object.isRequired
3058
+ };
3059
+ var TableEnd_default = TableEnd;
3060
+
3061
+ // src/components/TableHead.tsx
3062
+ var import_ink21 = require("ink");
3063
+ var import_prop_types17 = __toESM(require("prop-types"));
3064
+ var import_react15 = require("react");
3065
+ var import_jsx_runtime23 = require("react/jsx-runtime");
3066
+ var TableHead = ({ keyDetails, ...props }) => {
3067
+ let topLine = "\u250C";
3068
+ topLine += Object.values(keyDetails).map(({ width }) => "\u2500".repeat(width + 2)).join("\u252C");
3069
+ topLine += "\u2510";
3070
+ const contentLineElements = Object.values(keyDetails).map(
3071
+ ({ key, width }, index) => {
3072
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_react15.Fragment, { children: [
3073
+ index > 0 ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ink21.Text, { children: " \u2502 " }) : null,
3074
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ink21.Box, { width, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ink21.Text, { bold: true, children: key }) })
3075
+ ] }, key);
3076
+ }
3077
+ );
3078
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_ink21.Box, { flexDirection: "column", ...props, children: [
3079
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ink21.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ink21.Text, { children: topLine }) }),
3080
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ink21.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_ink21.Box, { children: [
3081
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ink21.Text, { children: "\u2502 " }),
3082
+ contentLineElements,
3083
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ink21.Text, { children: " \u2502" })
3084
+ ] }) })
3085
+ ] });
3086
+ };
3087
+ TableHead.propTypes = {
3088
+ bottomLinePrefix: import_prop_types17.default.string,
3089
+ keyDetails: import_prop_types17.default.object.isRequired
3090
+ };
3091
+ var TableHead_default = TableHead;
3092
+
3093
+ // src/components/TableBody.tsx
3094
+ var import_prop_types19 = __toESM(require("prop-types"));
3095
+
3096
+ // src/components/TableRow.tsx
3097
+ var import_ink22 = require("ink");
3098
+ var import_prop_types18 = __toESM(require("prop-types"));
3099
+ var import_jsx_runtime24 = require("react/jsx-runtime");
3100
+ var TableRow = ({
3101
+ data,
3102
+ getCellTextProps = ({ props }) => props,
3103
+ keyDetails,
3104
+ textProps = {}
3105
+ }) => {
3106
+ let topLine = "\u251C";
3107
+ topLine += Object.values(keyDetails).map(({ width }) => "\u2500".repeat(width + 2)).join("\u253C");
3108
+ topLine += "\u2524";
3109
+ const content = /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
3110
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ink22.Text, { children: "\u2502 " }),
3111
+ Object.entries(data).map(([key, value], index) => {
3112
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_ink22.Box, { children: [
3113
+ index > 0 ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ink22.Text, { children: " \u2502 " }) : null,
3114
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ink22.Box, { width: keyDetails[key].width, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
3115
+ import_ink22.Text,
3116
+ {
3117
+ ...getCellTextProps({
3118
+ key,
3119
+ props: textProps,
3120
+ rowData: data,
3121
+ value
3122
+ }),
3123
+ children: value
3124
+ }
3125
+ ) })
3126
+ ] }, key);
3127
+ }),
3128
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ink22.Text, { children: " \u2502" })
3129
+ ] });
3130
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_jsx_runtime24.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_ink22.Box, { flexDirection: "column", children: [
3131
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ink22.Text, { children: topLine }),
3132
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ink22.Box, { children: content })
3133
+ ] }) });
3134
+ };
3135
+ TableRow.propTypes = {
3136
+ data: import_prop_types18.default.object.isRequired,
3137
+ getCellTextProps: import_prop_types18.default.func,
3138
+ keyDetails: import_prop_types18.default.object.isRequired,
3139
+ textProps: import_prop_types18.default.object
3140
+ };
3141
+ var TableRow_default = TableRow;
3142
+
3143
+ // src/components/TableBody.tsx
3144
+ var import_jsx_runtime25 = require("react/jsx-runtime");
3145
+ var TableBody = ({ data, getCellTextProps, keyDetails }) => {
3146
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_jsx_runtime25.Fragment, { children: data.map((rowData, index) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
3147
+ TableRow_default,
3148
+ {
3149
+ data: rowData,
3150
+ getCellTextProps,
3151
+ keyDetails
3152
+ },
3153
+ index
3154
+ )) });
3155
+ };
3156
+ TableBody.propTypes = {
3157
+ data: (props, propName, componentName) => {
3158
+ const type = typeof props.data;
3159
+ if (Array.isArray("array")) {
3160
+ return new Error(
3161
+ `'data' prop must be an array ('${type}' given) in ${componentName} component`
3162
+ );
3163
+ }
3164
+ if (props.data.length === 0) {
3165
+ return new Error(
3166
+ `'data' prop must not be empty, in ${componentName} component`
3167
+ );
3168
+ }
3169
+ },
3170
+ getCellTextProps: import_prop_types19.default.func,
3171
+ keyDetails: import_prop_types19.default.object.isRequired
3172
+ };
3173
+ var TableBody_default = TableBody;
3174
+
3175
+ // src/utilities/getKeyDetails.ts
3176
+ var getKeyDetails_default = (data) => {
3177
+ const result = {};
3178
+ Object.keys(data[0]).forEach((key) => {
3179
+ result[key] = {
3180
+ key,
3181
+ width: Math.max(
3182
+ key.length,
3183
+ data.map((item) => item[key].toString().length).sort((a, b) => b - a)[0]
3184
+ )
3185
+ };
3186
+ });
3187
+ return result;
3188
+ };
3189
+
3190
+ // src/components/Table.tsx
3191
+ var import_jsx_runtime26 = require("react/jsx-runtime");
3192
+ var Table = ({ data, getCellTextProps }) => {
3193
+ const keyDetails = getKeyDetails_default(data);
3194
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_ink23.Box, { flexDirection: "column", children: [
3195
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(TableHead_default, { keyDetails }),
3196
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
3197
+ TableBody_default,
3198
+ {
3199
+ data,
3200
+ getCellTextProps,
3201
+ keyDetails
3202
+ }
3203
+ ),
3204
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(TableEnd_default, { keyDetails })
3205
+ ] });
3206
+ };
3207
+ Table.propTypes = {
3208
+ data: (props, propName, componentName) => {
3209
+ const type = typeof props.data;
3210
+ if (Array.isArray("array")) {
3211
+ return new Error(
3212
+ `'data' prop must be an array ('${type}' given) in ${componentName} component`
3213
+ );
3214
+ }
3215
+ if (props.data.length === 0) {
3216
+ return new Error(
3217
+ `'data' prop must not be empty, in ${componentName} component`
3218
+ );
3219
+ }
3220
+ },
3221
+ getCellTextProps: import_prop_types20.default.func
3222
+ };
3223
+ var Table_default = Table;
3224
+
3225
+ // src/utilities/capitalize.ts
3226
+ var capitalize_default = (input) => {
3227
+ if (typeof input !== "string") {
3228
+ return "";
3229
+ }
3230
+ return input.charAt(0).toUpperCase() + input.slice(1);
3231
+ };
3232
+
3233
+ // src/utilities/getBuilds.ts
3234
+ var getBuilds_default = async ({
3235
+ addWhereClauses = (query) => query,
3236
+ userId,
3237
+ appId,
3238
+ limit = 5,
3239
+ startAfter = null
3240
+ }) => {
3241
+ logger_default.debug({ appId, limit, startAfter }, "getBuilds");
3242
+ const appRef = firestore_default.doc(`users/${userId}/applications/${appId}`);
3243
+ const appSnapshot = await appRef.get();
3244
+ if (!appSnapshot.exists) {
3245
+ throw new Error(`Application with ID of ${appId} doesn't exist.`);
3246
+ }
3247
+ let query = addWhereClauses(appRef.collection("builds")).orderBy(
3248
+ "createdAt",
3249
+ "desc"
3250
+ );
3251
+ if (startAfter) {
3252
+ query = query.startAfter(startAfter);
3253
+ }
3254
+ const buildsResult = await query.limit(limit).get();
3255
+ if (buildsResult.empty) {
3256
+ return [];
3257
+ }
3258
+ return buildsResult.docs.map((doc) => doc.data());
3259
+ };
3260
+
3261
+ // src/utilities/getRelativeDateFromDateString.ts
3262
+ var dateFns = __toESM(require("date-fns"));
3263
+ var getRelativeDateFromDateString_default = (input) => dateFns.formatDistance(new Date(input), new Date()) + " ago";
3264
+
3265
+ // src/components/SyntaxHighlight.tsx
3266
+ var React3 = __toESM(require("react"));
3267
+ var import_prop_types21 = __toESM(require("prop-types"));
3268
+ var import_ink24 = require("ink");
3269
+ var import_cli_highlight = require("cli-highlight");
3270
+ var import_jsx_runtime27 = require("react/jsx-runtime");
3271
+ var SyntaxHighlight = ({ code, language }) => {
3272
+ const highlightedCode = React3.useMemo(() => {
3273
+ return (0, import_cli_highlight.highlight)(code, { language });
3274
+ }, [code, language]);
3275
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_ink24.Text, { children: highlightedCode });
3276
+ };
3277
+ SyntaxHighlight.propTypes = {
3278
+ code: import_prop_types21.default.string.isRequired,
3279
+ language: import_prop_types21.default.string
3280
+ };
3281
+ var SyntaxHighlight_default = SyntaxHighlight;
3282
+
3283
+ // src/components/ViewBuilds.tsx
3284
+ var import_jsx_runtime28 = require("react/jsx-runtime");
3285
+ var ViewBuilds = ({
3286
+ commandUsed,
3287
+ configPath,
3288
+ count,
3289
+ format,
3290
+ exit: shouldExitAfterViewingBuilds = false
3291
+ }) => {
3292
+ const exit = useExit_default();
3293
+ const [
3294
+ {
3295
+ builds,
3296
+ error,
3297
+ hasMoreToLoad,
3298
+ isInitialLoadComplete,
3299
+ isLoading,
3300
+ projectConfig,
3301
+ startAfter,
3302
+ user
3303
+ },
3304
+ setState
3305
+ ] = (0, import_react16.useState)({
3306
+ builds: [],
3307
+ error: null,
3308
+ hasMoreToLoad: true,
3309
+ isInitialLoadComplete: false,
3310
+ isLoading: false,
3311
+ projectConfig: null,
3312
+ startAfter: null,
3313
+ user: null
3314
+ });
3315
+ const onInput = useInput_default();
3316
+ onInput((input, key) => {
3317
+ if (key.escape) {
3318
+ exit();
3319
+ }
3320
+ if (input === " " || key.downArrow || key.pageDown || key.return) {
3321
+ if (!isInitialLoadComplete || !hasMoreToLoad) {
3322
+ return;
3323
+ }
3324
+ setState((previousState) => ({
3325
+ ...previousState,
3326
+ startAfter: builds[builds.length - 1].createdAt
3327
+ }));
3328
+ }
3329
+ });
3330
+ (0, import_react16.useEffect)(() => {
3331
+ async function viewBuilds() {
3332
+ if (error || isLoading || isInitialLoadComplete && (!hasMoreToLoad || !startAfter)) {
3333
+ return;
3334
+ }
3335
+ if (!["table", "json"].includes(format)) {
3336
+ setState((previousState) => ({
3337
+ ...previousState,
3338
+ error: new Error(
3339
+ `Invalid output format "${format}". Valid formats are "table" and "json".`
3340
+ )
3341
+ }));
3342
+ return;
3343
+ }
3344
+ setState((previousState) => ({
3345
+ ...previousState,
3346
+ isLoading: true
3347
+ }));
3348
+ let config2;
3349
+ try {
3350
+ config2 = projectConfig || getProjectConfig_default(configPath).config;
3351
+ } catch (e) {
3352
+ setState((previousState) => ({ ...previousState, error: e }));
3353
+ return;
3354
+ }
3355
+ const pageSize = count || 5;
3356
+ const user2 = await findAppUserId_default(config2.id);
3357
+ getBuilds_default({
3358
+ appId: config2.id,
3359
+ limit: pageSize + 1,
3360
+ startAfter,
3361
+ userId: user2.id
3362
+ }).then((buildsResult) => {
3363
+ setState((previousState) => {
3364
+ const stateUpdates = {
3365
+ builds: [],
3366
+ hasMoreToLoad: buildsResult.length > pageSize,
3367
+ isInitialLoadComplete: true,
3368
+ isLoading: false,
3369
+ projectConfig: config2,
3370
+ startAfter: null,
3371
+ user: user2
3372
+ };
3373
+ if (buildsResult.length) {
3374
+ stateUpdates.builds = [
3375
+ ...previousState.builds,
3376
+ ...buildsResult.slice(0, pageSize)
3377
+ ];
3378
+ }
3379
+ return {
3380
+ ...previousState,
3381
+ ...stateUpdates
3382
+ };
3383
+ });
3384
+ }).catch((e) => {
3385
+ logForCI_default(e);
3386
+ setState((previousState) => ({
3387
+ ...previousState,
3388
+ error: e,
3389
+ isLoading: false
3390
+ }));
3391
+ });
3392
+ }
3393
+ viewBuilds();
3394
+ }, [
3395
+ error,
3396
+ hasMoreToLoad,
3397
+ isInitialLoadComplete,
3398
+ isLoading,
3399
+ projectConfig,
3400
+ startAfter
3401
+ ]);
3402
+ (0, import_react16.useEffect)(() => {
3403
+ if (!hasMoreToLoad) {
3404
+ setTimeout(exit, 10);
3405
+ }
3406
+ }, [exit, hasMoreToLoad]);
3407
+ (0, import_react16.useEffect)(() => {
3408
+ if (!isLoading && isInitialLoadComplete && shouldExitAfterViewingBuilds) {
3409
+ setTimeout(exit, 10);
3410
+ }
3411
+ }, [exit, isLoading, shouldExitAfterViewingBuilds, isInitialLoadComplete]);
3412
+ if (error) {
3413
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ErrorDisplay_default, { commandUsed, error });
3414
+ }
3415
+ if (!isInitialLoadComplete) {
3416
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_ink25.Text, { children: "Loading..." });
3417
+ }
3418
+ if (builds.length) {
3419
+ const formatData = (builds2) => builds2.map((build) => ({
3420
+ ID: build.id,
3421
+ Version: build.appVersion || "n/a",
3422
+ "Creation date": getRelativeDateFromDateString_default(build.createdAt),
3423
+ Status: capitalize_default(build.status),
3424
+ "Release date": build.releasedAt ? getRelativeDateFromDateString_default(build.releasedAt) : "Unreleased",
3425
+ Owner: user ? user.label : "unknown"
3426
+ }));
3427
+ const getCellTextProps = ({ key, props, value }) => {
3428
+ const result = { ...props };
3429
+ if (key === "Status") {
3430
+ const status = value.toLowerCase();
3431
+ if (status === "succeeded") {
3432
+ result.color = "green";
3433
+ } else if (status === "failed") {
3434
+ result.color = "red";
3435
+ } else if (status === "cancelled") {
3436
+ result.dimColor = true;
3437
+ }
3438
+ } else if (key === "Version" && value.toLowerCase() === "n/a") {
3439
+ result.dimColor = true;
3440
+ } else if (key === "Release date" && value.toLowerCase() === "unreleased") {
3441
+ result.dimColor = true;
3442
+ }
3443
+ return result;
3444
+ };
3445
+ const removeAppBuilderLibConfig = (builds2) => {
3446
+ return builds2.map((build) => {
3447
+ if (build.mac && build.mac.appBuilderLibConfig) {
3448
+ delete build.mac.appBuilderLibConfig;
3449
+ }
3450
+ if (build.windows && build.windows.appBuilderLibConfig) {
3451
+ delete build.windows.appBuilderLibConfig;
3452
+ }
3453
+ if (build.linux && build.linux.appBuilderLibConfig) {
3454
+ delete build.linux.appBuilderLibConfig;
3455
+ }
3456
+ return build;
3457
+ });
3458
+ };
3459
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
3460
+ format === "table" ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3461
+ Table_default,
3462
+ {
3463
+ getCellTextProps,
3464
+ data: formatData(builds)
3465
+ }
3466
+ ) : null,
3467
+ format === "json" ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3468
+ SyntaxHighlight_default,
3469
+ {
3470
+ code: JSON.stringify(removeAppBuilderLibConfig(builds), null, 2),
3471
+ language: "JSON"
3472
+ }
3473
+ ) : null,
3474
+ !shouldExitAfterViewingBuilds && builds.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_ink25.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_ink25.Text, { color: "gray", dimColor: true, children: isLoading ? "Loading more..." : hasMoreToLoad ? `Showing the latest ${builds.length} builds. Press space/down to load more.` : `Showing all (${builds.length}) builds` }) }) : null
3475
+ ] });
3476
+ } else {
3477
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_ink25.Text, { children: "There are no builds yet" });
3478
+ }
3479
+ };
3480
+ ViewBuilds.propTypes = {
3481
+ commandUsed: import_prop_types22.default.string.isRequired,
3482
+ configPath: import_prop_types22.default.string,
3483
+ count: import_prop_types22.default.number,
3484
+ format: import_prop_types22.default.string,
3485
+ exit: import_prop_types22.default.bool
3486
+ };
3487
+ var ViewBuilds_default = ViewBuilds;
3488
+
3489
+ // src/commands/BuildsCommand.tsx
3490
+ var import_jsx_runtime29 = require("react/jsx-runtime");
3491
+ var BuildsCommand = ({
3492
+ id,
3493
+ shouldViewLatest,
3494
+ configPath = null,
3495
+ count,
3496
+ format = "table",
3497
+ exit
3498
+ }) => {
3499
+ checkIfReactIsUsable_default();
3500
+ useAnalyticsCommand("builds", {
3501
+ id,
3502
+ shouldViewLatest,
3503
+ configPath,
3504
+ count,
3505
+ format,
3506
+ exit
3507
+ });
3508
+ const getContents = () => {
3509
+ let commandUsed = "todesktop builds";
3510
+ if (id) {
3511
+ commandUsed += " <id>";
3512
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(ViewBuild_default, { commandUsed, id, configPath });
3513
+ } else if (shouldViewLatest) {
3514
+ commandUsed += " --latest";
3515
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3516
+ ViewBuild_default,
3517
+ {
3518
+ commandUsed,
3519
+ shouldViewLatest: true,
3520
+ configPath
3521
+ }
3522
+ );
3523
+ } else {
3524
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3525
+ ViewBuilds_default,
3526
+ {
3527
+ commandUsed,
3528
+ configPath,
3529
+ count,
3530
+ format,
3531
+ exit
3532
+ }
3533
+ );
3534
+ }
3535
+ };
3536
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(LoginHOC_default, { children: getContents() }) });
3537
+ };
3538
+ var BuildsCommand_default = BuildsCommand;
3539
+
3540
+ // src/commands/LogoutCommand.tsx
3541
+ var import_react17 = require("react");
3542
+ var import_ink26 = require("ink");
3543
+ var import_jsx_runtime30 = require("react/jsx-runtime");
3544
+ var Logout = () => {
3545
+ const exit = useExit_default();
3546
+ const { hasAttemptedTracking } = useAnalyticsCommand("logout");
3547
+ checkIfReactIsUsable_default();
3548
+ deleteAuthConfig();
3549
+ (0, import_react17.useEffect)(() => {
3550
+ if (hasAttemptedTracking) {
3551
+ exit();
3552
+ }
3553
+ }, [exit, hasAttemptedTracking]);
3554
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ink26.Text, { children: "Log out successful" });
3555
+ };
3556
+ var LogoutWrapper = () => {
3557
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(LoginHOC_default, { isInteractive: false, children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(Logout, {}) }) });
3558
+ };
3559
+ var LogoutCommand_default = LogoutWrapper;
3560
+
3561
+ // src/commands/ReleaseCommand.tsx
3562
+ var import_ink30 = require("ink");
3563
+
3564
+ // src/components/ReleaseBuild.tsx
3565
+ var import_ink27 = require("ink");
3566
+ var import_ink_link4 = __toESM(require("ink-link"));
3567
+ var import_ink_select_input2 = __toESM(require("ink-select-input"));
3568
+ var import_prop_types23 = __toESM(require("prop-types"));
3569
+ var import_react18 = require("react");
3570
+
3571
+ // src/utilities/getBuildById.ts
3572
+ var getBuildById_default = async ({ appId, buildId, userId }) => {
3573
+ logger_default.debug({ appId, buildId }, "getBuildById");
3574
+ const snapshot = await firestore_default.doc(`users/${userId}/applications/${appId}/builds/${buildId}`).get();
3575
+ if (!snapshot.exists) {
3576
+ return null;
3577
+ }
3578
+ return snapshot.data();
3579
+ };
3580
+
3581
+ // src/components/ReleaseBuild.tsx
3582
+ var import_jsx_runtime31 = require("react/jsx-runtime");
3583
+ var getValidationErrorMessageDisplay = (build, validationMessage) => {
3584
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_ink27.Box, { flexDirection: "column", children: [
3585
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_ink27.Text, { bold: true, color: "red", children: [
3586
+ "Can't release ",
3587
+ build.appName,
3588
+ " v",
3589
+ build.appVersion
3590
+ ] }),
3591
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ink27.Box, { marginBottom: 1, marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ink27.Text, { children: validationMessage }) }),
3592
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ink27.Text, { bold: true, children: "See web UI for more information: " }),
3593
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ink27.Text, { children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ink_link4.default, { fallback: false, url: build.url, children: build.url }) })
3594
+ ] });
3595
+ };
3596
+ var ReleaseBuild = ({ commandUsed, id, shouldConfirm, configPath }) => {
3597
+ const exit = useExit_default();
3598
+ const [
3599
+ {
3600
+ appId,
3601
+ arbitraryMessageComponent,
3602
+ build,
3603
+ error,
3604
+ hasConfirmed,
3605
+ hasBeenValidatedSuccesfully,
3606
+ isComplete,
3607
+ isReleasing
3608
+ },
3609
+ setState
3610
+ ] = (0, import_react18.useState)({
3611
+ appId: null,
3612
+ build: null,
3613
+ error: null,
3614
+ hasConfirmed: false,
3615
+ hasBeenValidatedSuccesfully: false,
3616
+ isComplete: false,
3617
+ isReleasing: false,
3618
+ arbitraryMessageComponent: null
3619
+ });
3620
+ const onError = (e) => {
3621
+ const error2 = e.response ? e.response.data : e;
3622
+ logForCI_default(error2);
3623
+ setState((prevState) => ({
3624
+ ...prevState,
3625
+ error: error2
3626
+ }));
3627
+ };
3628
+ (0, import_react18.useEffect)(() => {
3629
+ async function releaseBuild() {
3630
+ if (build) {
3631
+ return;
3632
+ }
3633
+ const config2 = getProjectConfig_default(configPath).config;
3634
+ const appId2 = config2.id;
3635
+ const { id: userId } = await findAppUserId_default(appId2);
3636
+ const loadBuild = (buildId) => {
3637
+ getBuildById_default({ appId: appId2, buildId, userId }).then((buildResult) => {
3638
+ if (!buildResult) {
3639
+ throw new Error(
3640
+ `No such build ${buildId} for application ${appId2}`
3641
+ );
3642
+ }
3643
+ setState((previousState) => ({
3644
+ ...previousState,
3645
+ appId: appId2,
3646
+ build: buildResult
3647
+ }));
3648
+ }).catch(onError);
3649
+ };
3650
+ if (id) {
3651
+ loadBuild(id);
3652
+ } else {
3653
+ getLatestBuildId_default({ appId: appId2, userId }).catch(onError).then((buildId) => {
3654
+ if (!buildId) {
3655
+ setState((previousState) => ({
3656
+ ...previousState,
3657
+ arbitraryMessageComponent: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ink27.Text, { children: "There are no builds yet" })
3658
+ }));
3659
+ return;
3660
+ }
3661
+ return loadBuild(buildId);
3662
+ });
3663
+ }
3664
+ }
3665
+ releaseBuild();
3666
+ }, [build, id]);
3667
+ (0, import_react18.useEffect)(() => {
3668
+ if (!build || hasBeenValidatedSuccesfully || arbitraryMessageComponent) {
3669
+ return;
3670
+ }
3671
+ let validationMessage;
3672
+ if (build.releasedAt) {
3673
+ validationMessage = `It has already been released.`;
3674
+ } else if (build.status !== "succeeded") {
3675
+ validationMessage = `The build must have completed successfully. Actual build status: ${build.status}`;
3676
+ }
3677
+ if (validationMessage) {
3678
+ setState((previousState) => ({
3679
+ ...previousState,
3680
+ arbitraryMessageComponent: getValidationErrorMessageDisplay(
3681
+ build,
3682
+ validationMessage
3683
+ ),
3684
+ hasBeenValidatedSuccesfully: false
3685
+ }));
3686
+ } else {
3687
+ setState((previousState) => ({
3688
+ ...previousState,
3689
+ hasBeenValidatedSuccesfully: true
3690
+ }));
3691
+ }
3692
+ }, [arbitraryMessageComponent, build, hasBeenValidatedSuccesfully]);
3693
+ (0, import_react18.useEffect)(() => {
3694
+ if (!hasBeenValidatedSuccesfully || shouldConfirm && !hasConfirmed || isReleasing || isComplete) {
3695
+ return;
3696
+ }
3697
+ setState((previousState) => ({
3698
+ ...previousState,
3699
+ isReleasing: true
3700
+ }));
3701
+ getCallableFirebaseFunction_default("releaseBuild")({
3702
+ appId,
3703
+ buildId: build.id
3704
+ }).then(() => {
3705
+ logForCI_default("Released!");
3706
+ setState((previousState) => ({
3707
+ ...previousState,
3708
+ isReleasing: false,
3709
+ isComplete: true
3710
+ }));
3711
+ }).catch((e) => {
3712
+ if (["failed-precondition", "not-found"].includes(e.code)) {
3713
+ setState((previousState) => ({
3714
+ ...previousState,
3715
+ arbitraryMessageComponent: getValidationErrorMessageDisplay(
3716
+ build,
3717
+ e.message
3718
+ ),
3719
+ isReleasing: false
3720
+ }));
3721
+ } else {
3722
+ onError(new Error("Unexpected internal error while releasing build"));
3723
+ }
3724
+ });
3725
+ }, [
3726
+ appId,
3727
+ build,
3728
+ hasConfirmed,
3729
+ hasBeenValidatedSuccesfully,
3730
+ id,
3731
+ isComplete,
3732
+ isReleasing,
3733
+ shouldConfirm
3734
+ ]);
3735
+ (0, import_react18.useEffect)(() => {
3736
+ if (isComplete) {
3737
+ setTimeout(exit, 10);
3738
+ return;
3739
+ }
3740
+ if (arbitraryMessageComponent) {
3741
+ setTimeout(() => exit(new Error("Validation failed")), 10);
3742
+ }
3743
+ }, [arbitraryMessageComponent, exit, isComplete]);
3744
+ if (error) {
3745
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ErrorDisplay_default, { commandUsed, error });
3746
+ }
3747
+ if (arbitraryMessageComponent) {
3748
+ return arbitraryMessageComponent;
3749
+ }
3750
+ if (hasBeenValidatedSuccesfully && shouldConfirm && !hasConfirmed) {
3751
+ const items = [
3752
+ {
3753
+ label: "Yes",
3754
+ value: "yes"
3755
+ },
3756
+ {
3757
+ label: "No",
3758
+ value: "no"
3759
+ }
3760
+ ];
3761
+ const onSubmit = (item) => {
3762
+ if (item.value === "no") {
3763
+ setTimeout(exit, 10);
3764
+ return;
3765
+ }
3766
+ setState((previousState) => ({
3767
+ ...previousState,
3768
+ hasConfirmed: true
3769
+ }));
3770
+ };
3771
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_ink27.Box, { flexDirection: "column", children: [
3772
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_ink27.Box, { children: [
3773
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_ink27.Text, { children: [
3774
+ "This will release build ",
3775
+ build.id,
3776
+ " as "
3777
+ ] }),
3778
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_ink27.Text, { bold: true, children: [
3779
+ build.appName,
3780
+ " v",
3781
+ build.appVersion
3782
+ ] }),
3783
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ink27.Text, { children: ", are you sure?" })
3784
+ ] }),
3785
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ink27.Box, { marginBottom: 1, marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
3786
+ import_ink_select_input2.default,
3787
+ {
3788
+ indicatorComponent: CustomSelectInputIndicator_default,
3789
+ initialIndex: 1,
3790
+ itemComponent: CustomSelectInputItem_default,
3791
+ items,
3792
+ onSelect: onSubmit
3793
+ }
3794
+ ) }),
3795
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ink27.Text, { dimColor: true, children: "Your users will be auto-updated to this version. You can use --force to bypass this confirmation in future" })
3796
+ ] });
3797
+ }
3798
+ if (isReleasing) {
3799
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ink27.Text, { children: "Releasing..." });
3800
+ }
3801
+ if (isComplete) {
3802
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ink27.Text, { bold: true, color: "greenBright", children: "Released!" });
3803
+ }
3804
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ink27.Text, { children: "..." });
3805
+ };
3806
+ ReleaseBuild.propTypes = {
3807
+ id: (props, propName, componentName) => {
3808
+ if ([props.id, props.shouldReleaseLatest].filter(Boolean).length !== 1) {
3809
+ return new Error(
3810
+ `Exactly one of 'id' and 'shouldReleaseLatest' must be specified in '${componentName}'`
3811
+ );
3812
+ }
3813
+ const type = typeof props.id;
3814
+ if (!["string", "undefined"].includes(type)) {
3815
+ return new Error(
3816
+ `'id' is a '${type}', not a string, in '${componentName}'.`
3817
+ );
3818
+ }
3819
+ },
3820
+ commandUsed: import_prop_types23.default.string.isRequired,
3821
+ shouldReleaseLatest: (props, propName, componentName) => {
3822
+ if ([props.id, props.shouldReleaseLatest].filter(Boolean).length !== 1) {
3823
+ return new Error(
3824
+ `Exactly one of 'id' and 'shouldReleaseLatest' must be specified in '${componentName}'`
3825
+ );
3826
+ }
3827
+ const type = typeof props.shouldReleaseLatest;
3828
+ if (!["boolean", "undefined"].includes(type)) {
3829
+ return new Error(
3830
+ `'shouldReleaseLatest' is a '${type}', not a boolean, in '${componentName}'.`
3831
+ );
3832
+ }
3833
+ },
3834
+ shouldConfirm: import_prop_types23.default.bool.isRequired,
3835
+ configPath: import_prop_types23.default.string
3836
+ };
3837
+ var ReleaseBuild_default = ReleaseBuild;
3838
+
3839
+ // src/components/PickBuildForRelease.tsx
3840
+ var import_ink29 = require("ink");
3841
+ var import_prop_types25 = __toESM(require("prop-types"));
3842
+ var import_react19 = require("react");
3843
+
3844
+ // src/components/SelectTable.tsx
3845
+ var import_ink28 = require("ink");
3846
+ var import_ink_select_input3 = __toESM(require("ink-select-input"));
3847
+ var import_prop_types24 = __toESM(require("prop-types"));
3848
+ var import_jsx_runtime32 = require("react/jsx-runtime");
3849
+ var CustomIndicator = (props) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(CustomSelectInputIndicator_default, { marginTop: 1, ...props });
3850
+ var SelectTable = ({ data, onSelect }) => {
3851
+ const keyDetails = getKeyDetails_default(data);
3852
+ const getSelectItems = () => {
3853
+ return data.map((rowData, index) => {
3854
+ return {
3855
+ index,
3856
+ key: index,
3857
+ value: rowData
3858
+ };
3859
+ });
3860
+ };
3861
+ const ItemComponent = ({
3862
+ label,
3863
+ index,
3864
+ isSelected
3865
+ }) => {
3866
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
3867
+ TableRow_default,
3868
+ {
3869
+ data: data[index],
3870
+ keyDetails,
3871
+ textProps: { bold: isSelected, color: isSelected ? void 0 : "gray" }
3872
+ },
3873
+ index
3874
+ );
3875
+ };
3876
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_ink28.Box, { flexDirection: "column", children: [
3877
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(TableHead_default, { keyDetails, marginLeft: 2 }),
3878
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
3879
+ import_ink_select_input3.default,
3880
+ {
3881
+ indicatorComponent: CustomIndicator,
3882
+ itemComponent: ItemComponent,
3883
+ items: getSelectItems(),
3884
+ onSelect
3885
+ }
3886
+ ),
3887
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(TableEnd_default, { keyDetails, marginLeft: 2 })
3888
+ ] });
3889
+ };
3890
+ SelectTable.propTypes = {
3891
+ data: (props, propName, componentName) => {
3892
+ const type = typeof props.data;
3893
+ if (Array.isArray("array")) {
3894
+ return new Error(
3895
+ `'data' prop must be an array ('${type}' given) in ${componentName} component`
3896
+ );
3897
+ }
3898
+ if (props.data.length === 0) {
3899
+ return new Error(
3900
+ `'data' prop must not be empty, in ${componentName} component`
3901
+ );
3902
+ }
3903
+ },
3904
+ onSelect: import_prop_types24.default.func
3905
+ };
3906
+ var SelectTable_default = SelectTable;
3907
+
3908
+ // src/components/PickBuildForRelease.tsx
3909
+ var import_jsx_runtime33 = require("react/jsx-runtime");
3910
+ var PickBuildForRelease = ({ commandUsed, shouldConfirm, configPath }) => {
3911
+ const exit = useExit_default();
3912
+ const [{ error, builds, chosenBuildId, isLoading, user }, setState] = (0, import_react19.useState)({
3913
+ error: null,
3914
+ builds: null,
3915
+ chosenBuildId: null,
3916
+ isLoading: true,
3917
+ user: null
3918
+ });
3919
+ const onInput = useInput_default();
3920
+ (0, import_react19.useEffect)(() => {
3921
+ async function pickBuildForRelease() {
3922
+ if (builds) {
3923
+ return;
3924
+ }
3925
+ let config2;
3926
+ try {
3927
+ config2 = getProjectConfig_default(configPath).config;
3928
+ } catch (e) {
3929
+ setState((previousState) => ({ ...previousState, error: e }));
3930
+ return;
3931
+ }
3932
+ const user2 = await findAppUserId_default(config2.id);
3933
+ getBuilds_default({
3934
+ addWhereClauses: (query) => query.where("status", "==", "succeeded"),
3935
+ appId: config2.id,
3936
+ limit: 50,
3937
+ userId: user2.id
3938
+ }).then((buildsResult) => {
3939
+ setState((previousState) => ({
3940
+ ...previousState,
3941
+ user: user2,
3942
+ builds: buildsResult.filter((buildResult) => !buildResult.releasedAt).slice(0, 5),
3943
+ isLoading: false
3944
+ }));
3945
+ }).catch((e) => {
3946
+ logForCI_default(e);
3947
+ setState((previousState) => ({
3948
+ ...previousState,
3949
+ error: e,
3950
+ isLoading: false
3951
+ }));
3952
+ });
3953
+ }
3954
+ pickBuildForRelease();
3955
+ }, [builds]);
3956
+ onInput((input, key) => {
3957
+ if (!builds || !builds.length || chosenBuildId) {
3958
+ return;
3959
+ }
3960
+ if (key.escape) {
3961
+ exit();
3962
+ }
3963
+ });
3964
+ if (error) {
3965
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(ErrorDisplay_default, { commandUsed, error });
3966
+ }
3967
+ if (isLoading) {
3968
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ink29.Text, { children: "Loading..." });
3969
+ }
3970
+ if (chosenBuildId) {
3971
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
3972
+ ReleaseBuild_default,
3973
+ {
3974
+ commandUsed,
3975
+ id: chosenBuildId,
3976
+ shouldConfirm
3977
+ }
3978
+ );
3979
+ }
3980
+ if (builds.length) {
3981
+ const data = builds.map((build) => ({
3982
+ ID: build.id,
3983
+ Version: build.appVersion || "unknown",
3984
+ "Creation date": getRelativeDateFromDateString_default(build.createdAt),
3985
+ Owner: user ? user.label : "unknown"
3986
+ }));
3987
+ const onSelect = (item) => setState((previousState) => ({
3988
+ ...previousState,
3989
+ chosenBuildId: item.value.ID
3990
+ }));
3991
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
3992
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ink29.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ink29.Text, { children: "Which build would you like to release?" }) }),
3993
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(SelectTable_default, { data, onSelect }),
3994
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ink29.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_ink29.Text, { dimColor: true, children: [
3995
+ "Showing the latest ",
3996
+ builds.length,
3997
+ " unreleased successful builds"
3998
+ ] }) })
3999
+ ] });
4000
+ } else {
4001
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_ink29.Box, { children: [
4002
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ink29.Text, { children: "No elligible builds found " }),
4003
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ink29.Text, { dimColor: true, children: "(i.e. unreleased and successful)" })
4004
+ ] });
4005
+ }
4006
+ };
4007
+ PickBuildForRelease.propTypes = {
4008
+ commandUsed: import_prop_types25.default.string.isRequired,
4009
+ shouldConfirm: import_prop_types25.default.bool.isRequired,
4010
+ configPath: import_prop_types25.default.string
4011
+ };
4012
+ var PickBuildForRelease_default = PickBuildForRelease;
4013
+
4014
+ // src/commands/ReleaseCommand.tsx
4015
+ var import_jsx_runtime34 = require("react/jsx-runtime");
4016
+ var ReleaseCommand = ({
4017
+ id,
4018
+ force,
4019
+ shouldReleaseLatest,
4020
+ configPath
4021
+ }) => {
4022
+ checkIfReactIsUsable_default();
4023
+ useAnalyticsCommand("release", {
4024
+ id,
4025
+ force,
4026
+ shouldReleaseLatest,
4027
+ config: configPath
4028
+ });
4029
+ if (id && id.startsWith(".")) {
4030
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
4031
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_ink30.Box, { children: [
4032
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ink30.Text, { bold: true, color: "red", children: "todesktop release <project-path>" }),
4033
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ink30.Text, { children: " is no longer supported. Run this instead:" })
4034
+ ] }),
4035
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ink30.Text, { bold: true, color: "greenBright", children: "todesktop build && todesktop release --latest --force" })
4036
+ ] });
4037
+ }
4038
+ const getContents = () => {
4039
+ if (id) {
4040
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
4041
+ ReleaseBuild_default,
4042
+ {
4043
+ configPath,
4044
+ commandUsed: "todesktop release <id>",
4045
+ id,
4046
+ shouldConfirm: !force
4047
+ }
4048
+ );
4049
+ } else if (shouldReleaseLatest) {
4050
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
4051
+ ReleaseBuild_default,
4052
+ {
4053
+ configPath,
4054
+ commandUsed: "todesktop release --latest",
4055
+ shouldReleaseLatest: true,
4056
+ shouldConfirm: !force
4057
+ }
4058
+ );
4059
+ } else {
4060
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
4061
+ PickBuildForRelease_default,
4062
+ {
4063
+ configPath,
4064
+ commandUsed: "todesktop builds",
4065
+ shouldConfirm: !force
4066
+ }
4067
+ );
4068
+ }
4069
+ };
4070
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(LoginHOC_default, { children: getContents() }) });
4071
+ };
4072
+ var ReleaseCommand_default = ReleaseCommand;
4073
+
4074
+ // src/commands/WhoamiCommand.tsx
4075
+ var import_react20 = require("react");
4076
+ var import_ink31 = require("ink");
4077
+ var import_jsx_runtime35 = require("react/jsx-runtime");
4078
+ var WhoAmI = () => {
4079
+ const exit = useExit_default();
4080
+ checkIfReactIsUsable_default();
4081
+ const auth = getAuthConfig();
4082
+ const { hasAttemptedTracking } = useAnalyticsCommand("whoami", {}, {});
4083
+ (0, import_react20.useEffect)(() => {
4084
+ if (hasAttemptedTracking) {
4085
+ exit();
4086
+ }
4087
+ }, [exit, hasAttemptedTracking]);
4088
+ if (!auth || !auth.email) {
4089
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ink31.Text, { children: "You're not signed in" });
4090
+ }
4091
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ink31.Text, { children: auth.email });
4092
+ };
4093
+ var WhoAmIWrapper = () => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(LoginHOC_default, { isInteractive: false, children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(WhoAmI, {}) }) });
4094
+ var WhoamiCommand_default = WhoAmIWrapper;
4095
+
4096
+ // src/utilities/exitIfCLIOutOfDate.ts
4097
+ var import_chalk2 = __toESM(require("chalk"));
4098
+ var import_is_installed_globally = __toESM(require("is-installed-globally"));
4099
+ var import_latest_version = __toESM(require("latest-version"));
4100
+ var import_semver = __toESM(require("semver"));
4101
+ var exitIfCLIOutOfDate_default = () => {
4102
+ if (process.env.AVA_PATH) {
4103
+ return;
4104
+ }
4105
+ const pkg = getToDesktopPackageJson();
4106
+ (0, import_latest_version.default)(pkg.name).then((latest) => {
4107
+ if (import_semver.default.gt(latest, pkg.version)) {
4108
+ const commandToUpdate = import_chalk2.default.greenBright(
4109
+ `npm install ${import_is_installed_globally.default ? "--location=global" : "--save-dev"} @todesktop/cli`
4110
+ );
4111
+ console.log(
4112
+ `Your version of @todesktop/cli is out of date.
4113
+ Run ${commandToUpdate} to update to v${latest}.`
4114
+ );
4115
+ if (!import_semver.default.satisfies(latest, `^${pkg.version}`)) {
4116
+ console.log(`CLI is exiting because it is out out of date.`);
4117
+ process.exit(1);
4118
+ }
4119
+ }
4120
+ });
4121
+ };
4122
+
4123
+ // src/utilities/initSentry.ts
4124
+ var Sentry3 = __toESM(require("@sentry/node"));
4125
+ var import_os2 = __toESM(require("os"));
4126
+
4127
+ // package.json
4128
+ var package_default = {
4129
+ private: false,
4130
+ publishConfig: {
4131
+ access: "public"
4132
+ },
4133
+ name: "@todesktop/cli",
4134
+ version: "1.5.1",
4135
+ license: "MIT",
4136
+ author: "Dave Jeffery <dave@todesktop.com> (http://www.todesktop.com/)",
4137
+ homepage: "https://todesktop.com/cli",
4138
+ bugs: {
4139
+ email: "dave@todesktop.com"
4140
+ },
4141
+ bin: {
4142
+ todesktop: "./dist/cli.js"
4143
+ },
4144
+ engines: {
4145
+ node: ">=12"
4146
+ },
4147
+ scripts: {
4148
+ dev: "cp-cli dev.env .env && npm run build:dev && npm link",
4149
+ "dev:prod": "cp-cli prod.env .env && npm run build && npm link",
4150
+ build: "esbuild src/index.ts --packages=external --bundle --sourcemap --platform=node --outfile=dist/cli.js && cp-cli prod.env .env",
4151
+ "build:dev": "esbuild src/index.ts --packages=external --bundle --sourcemap --platform=node --outfile=dist/cli.js && cp-cli prod.env .env",
4152
+ lint: "npm run lint:types && npm run lint:styles",
4153
+ "lint:styles": "eslint src test .eslintrc.js",
4154
+ "lint:types": "tsc",
4155
+ "lint--fix": "eslint src test --fix",
4156
+ release: "npm run build && npx np --tag=latest",
4157
+ "release-beta": "npm run build && npx np --any-branch --no-tests --tag=beta",
4158
+ test: "ava",
4159
+ "test--watch": "npm test -- --watch"
4160
+ },
4161
+ files: [
4162
+ "dist",
4163
+ "docs",
4164
+ ".env",
4165
+ "LICENSE",
4166
+ "README.md"
4167
+ ],
4168
+ dependencies: {
4169
+ "@sentry/node": "^5.27.2",
4170
+ ajv: "^8.11.2",
4171
+ "ajv-formats": "^2.1.1",
4172
+ "analytics-node": "^4.0.1",
4173
+ archiver: "^5.2.0",
4174
+ axios: "^0.21.1",
4175
+ "better-ajv-errors": "^1.2.0",
4176
+ bunyan: "^1.8.14",
4177
+ chalk: "^4.1.0",
4178
+ "cli-highlight": "^2.1.11",
4179
+ commander: "^9.4.1",
4180
+ conf: "^7.1.2",
4181
+ "date-fns": "^2.28.0",
4182
+ del: "^6.0.0",
4183
+ dotenv: "^8.2.0",
4184
+ du: "^1.0.0",
4185
+ "email-regex": "^4.0.0",
4186
+ "fast-glob": "^3.2.4",
4187
+ "final-form": "^4.20.1",
4188
+ "find-up": "^5.0.0",
4189
+ firebase: "^7.24.0",
4190
+ "git-rev-sync": "^3.0.2",
4191
+ ink: "^3.2.0",
4192
+ "ink-gradient": "^2.0.0",
4193
+ "ink-link": "^2.0.0",
4194
+ "ink-select-input": "^4.2.1",
4195
+ "ink-text-input": "^4.0.1",
4196
+ "is-ci": "^3.0.1",
4197
+ "is-installed-globally": "^0.3.2",
4198
+ "latest-version": "^5.1.0",
4199
+ "lodash.merge": "^4.6.2",
4200
+ "lodash.throttle": "^4.1.1",
4201
+ "parse-author": "^2.0.0",
4202
+ "pkg-up": "^3.1.0",
4203
+ "pretty-bytes": "^5.4.1",
4204
+ "prop-types": "^15.7.2",
4205
+ react: "^17.0.2",
4206
+ "react-final-form": "^6.5.1",
4207
+ semver: "^7.3.2",
4208
+ "source-map-support": "^0.5.21",
4209
+ "stream-to-array": "^2.3.0",
4210
+ superagent: "^7.1.3",
4211
+ uuid: "^8.3.1",
4212
+ "xdg-basedir": "^4.0.0"
4213
+ },
4214
+ devDependencies: {
4215
+ "@types/bunyan": "^1.8.6",
4216
+ "@types/react": "^18.0.26",
4217
+ "@typescript-eslint/eslint-plugin": "^5.46.1",
4218
+ "@typescript-eslint/parser": "^5.46.1",
4219
+ ava: "^4.3.1",
4220
+ "cp-cli": "^2.0.0",
4221
+ esbuild: "^0.16.5",
4222
+ "esbuild-register": "^3.4.1",
4223
+ eslint: "^8.29.0",
4224
+ "eslint-config-prettier": "^8.5.0",
4225
+ "eslint-plugin-import": "^2.26.0",
4226
+ "eslint-plugin-node": "^11.1.0",
4227
+ "eslint-plugin-prettier": "^4.2.1",
4228
+ "eslint-plugin-promise": "^6.1.1",
4229
+ "eslint-plugin-react": "^7.31.11",
4230
+ "eslint-plugin-react-hooks": "^4.6.0",
4231
+ "eslint-plugin-standard": "^4.1.0",
4232
+ execa: "^4.0.3",
4233
+ "extract-zip": "^2.0.1",
4234
+ "fs-extra": "^9.0.1",
4235
+ husky: "^4.3.0",
4236
+ "lint-staged": "^10.2.11",
4237
+ prettier: "^2.8.1",
4238
+ proxyquire: "^2.1.3",
4239
+ sinon: "^9.0.3",
4240
+ typescript: "^4.9.4"
4241
+ },
4242
+ ava: {
4243
+ extensions: [
4244
+ "ts"
4245
+ ],
4246
+ files: [
4247
+ "test/**/*.ts",
4248
+ "!test/fixtures/**/*",
4249
+ "!test/utilities/**/*"
4250
+ ],
4251
+ require: [
4252
+ "esbuild-register"
4253
+ ],
4254
+ timeout: "10m"
4255
+ },
4256
+ "lint-staged": {
4257
+ "**/*.{js,ts,tsx}": [
4258
+ "eslint src --fix",
4259
+ "git add"
4260
+ ]
4261
+ },
4262
+ husky: {
4263
+ hooks: {
4264
+ "pre-commit": "lint-staged"
4265
+ }
4266
+ },
4267
+ overrides: {
4268
+ pastel: {
4269
+ "parcel-bundler": {
4270
+ deasync: "0.1.27"
4271
+ }
4272
+ }
4273
+ },
4274
+ resolutions: {
4275
+ "pastel/parcel-bundler/deasync": "0.1.24",
4276
+ ink: "3.2.0",
4277
+ react: "17.0.2"
4278
+ },
4279
+ packageExtensions: {
4280
+ "ink-progress-bar@*": {
4281
+ dependencies: {
4282
+ react: "*",
4283
+ ink: "*"
4284
+ }
4285
+ }
4286
+ }
4287
+ };
4288
+
4289
+ // src/utilities/initSentry.ts
4290
+ var initSentry_default = () => {
4291
+ const { TODESKTOP_CLI_SENTRY_DSN } = getEnvironmentVariables_default();
4292
+ if (!TODESKTOP_CLI_SENTRY_DSN) {
4293
+ return;
4294
+ }
4295
+ Sentry3.init({
4296
+ dsn: TODESKTOP_CLI_SENTRY_DSN
4297
+ });
4298
+ Sentry3.configureScope((scope) => {
4299
+ try {
4300
+ scope.setUser({ email: getAuthConfig().email });
4301
+ } catch (err) {
4302
+ }
4303
+ try {
4304
+ scope.setContext("os", {
4305
+ name: import_os2.default.platform(),
4306
+ version: import_os2.default.release()
4307
+ });
4308
+ } catch (err) {
4309
+ }
4310
+ try {
4311
+ scope.setContext("runtime", {
4312
+ name: "Node.js",
4313
+ version: process.version
4314
+ });
4315
+ } catch (err) {
4316
+ }
4317
+ try {
4318
+ scope.setContext("app", {
4319
+ app_name: package_default.name,
4320
+ app_version: package_default.version
4321
+ });
4322
+ } catch (err) {
4323
+ }
4324
+ try {
4325
+ const configResult = getProjectConfig_default();
4326
+ scope.setExtra("config", configResult);
4327
+ } catch (err) {
4328
+ }
4329
+ });
4330
+ };
4331
+
4332
+ // src/utilities/onCommand.ts
4333
+ var onCommand_default = ({ sentry = true, exitIfOutOfDate = true } = {}) => {
4334
+ if (sentry) {
4335
+ initSentry_default();
4336
+ }
4337
+ if (exitIfOutOfDate) {
4338
+ exitIfCLIOutOfDate_default();
4339
+ }
4340
+ };
4341
+
4342
+ // src/index.ts
4343
+ var parseCount = (value) => {
4344
+ const parsedValue = Number.parseInt(value, 10);
4345
+ if (parsedValue > 0) {
4346
+ return parsedValue;
4347
+ }
4348
+ throw new import_commander.InvalidArgumentError("Should be a positive number");
4349
+ };
4350
+ import_commander.program.name("todekstop").version(getCliVersion_default());
4351
+ import_commander.program.command("build").description(
4352
+ "Build an Electron app with native installers, code signing baked-in, etc. but without releasing it (existing users won't get an auto-update). For quicker builds, you can append `--code-sign=false` to disablecode-signing and notarization."
4353
+ ).option(
4354
+ "--code-sign [bool]",
4355
+ "Whether or not code-signing and notarization should be performed. Disable this for quicker builds"
4356
+ ).option(
4357
+ "--config [string]",
4358
+ "Path to a different configuration file. If not specified, `todesktop.json` in the project root will be used"
4359
+ ).action(({ codeSign, config: config2 }) => {
4360
+ runCommand(BuildCommand_default, {
4361
+ shouldCodeSign: codeSign,
4362
+ configPath: config2
4363
+ });
4364
+ });
4365
+ import_commander.program.command("builds").description("View your builds").argument("[id]", "View a specific build by ID").option("--latest", "View the latest build").option(
4366
+ "--config [string]",
4367
+ "Path to a different configuration file. If not specified, `todesktop.json` in the project root will be used"
4368
+ ).option("--count [number]", "Number of builds to show per page", parseCount).addOption(
4369
+ new import_commander.Option("--format <type>", "Format to output the build details in").choices(["json", "table"]).default("table")
4370
+ ).option("--exit", "View the latest build").action((id, { config: config2, count, exit, format, latest }) => {
4371
+ runCommand(BuildsCommand_default, {
4372
+ configPath: config2,
4373
+ count,
4374
+ exit,
4375
+ format,
4376
+ id,
4377
+ shouldViewLatest: latest
4378
+ });
4379
+ });
4380
+ import_commander.program.command("logout").description("Logs you out").action(() => {
4381
+ runCommand(LogoutCommand_default, null, { exitIfOutOfDate: false });
4382
+ });
4383
+ import_commander.program.command("release").description("Release a build").argument("[id]", "A specific build ID to release").option("--force", "Skips interactive confirmation step").option("--latest", "Release the latest build").option(
4384
+ "--config [string]",
4385
+ "Path to a different configuration file. If not specified, `todesktop.json` in the project root will be used"
4386
+ ).action((id, { config: config2, force, latest }) => {
4387
+ runCommand(ReleaseCommand_default, {
4388
+ configPath: config2,
4389
+ force,
4390
+ id,
4391
+ shouldReleaseLatest: latest
4392
+ });
4393
+ });
4394
+ import_commander.program.command("whoami").description("Prints the email of the account you're signed into").action(() => {
4395
+ runCommand(WhoamiCommand_default);
4396
+ });
4397
+ var runCommand = (component, props = null, { exitIfOutOfDate = true } = {}) => {
4398
+ onCommand_default({ exitIfOutOfDate });
4399
+ const { waitUntilExit } = (0, import_ink32.render)((0, import_react21.createElement)(component, props));
4400
+ waitUntilExit().catch((error) => {
4401
+ console.error(error.stack);
4402
+ });
4403
+ };
4404
+ var args = [...process.argv];
4405
+ if (args.length === 2) {
4406
+ args.push("help");
4407
+ }
4408
+ import_commander.program.parse(args);
4409
+ //# sourceMappingURL=cli.js.map