preflightlaunch 0.2.0 → 0.2.1

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.
@@ -3,6 +3,7 @@ import { createRequire } from "node:module";
3
3
  const require = createRequire(import.meta.url);
4
4
  import {
5
5
  __commonJS,
6
+ __require,
6
7
  __toESM,
7
8
  init_esm_shims,
8
9
  open_default
@@ -2980,7 +2981,7 @@ var require_compile = __commonJS({
2980
2981
  const schOrFunc = root.refs[ref];
2981
2982
  if (schOrFunc)
2982
2983
  return schOrFunc;
2983
- let _sch = resolve3.call(this, root, ref);
2984
+ let _sch = resolve4.call(this, root, ref);
2984
2985
  if (_sch === void 0) {
2985
2986
  const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
2986
2987
  const { schemaId } = this.opts;
@@ -3007,7 +3008,7 @@ var require_compile = __commonJS({
3007
3008
  function sameSchemaEnv(s1, s2) {
3008
3009
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
3009
3010
  }
3010
- function resolve3(root, ref) {
3011
+ function resolve4(root, ref) {
3011
3012
  let sch;
3012
3013
  while (typeof (sch = this.refs[ref]) == "string")
3013
3014
  ref = sch;
@@ -3585,7 +3586,7 @@ var require_fast_uri = __commonJS({
3585
3586
  }
3586
3587
  return uri;
3587
3588
  }
3588
- function resolve3(baseURI, relativeURI, options) {
3589
+ function resolve4(baseURI, relativeURI, options) {
3589
3590
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3590
3591
  const resolved = resolveComponent(parse(baseURI, schemelessOptions), parse(relativeURI, schemelessOptions), schemelessOptions, true);
3591
3592
  schemelessOptions.skipEscape = true;
@@ -3812,7 +3813,7 @@ var require_fast_uri = __commonJS({
3812
3813
  var fastUri = {
3813
3814
  SCHEMES,
3814
3815
  normalize,
3815
- resolve: resolve3,
3816
+ resolve: resolve4,
3816
3817
  resolveComponent,
3817
3818
  equal,
3818
3819
  serialize,
@@ -10245,7 +10246,7 @@ var require_compile2 = __commonJS({
10245
10246
  const schOrFunc = root.refs[ref];
10246
10247
  if (schOrFunc)
10247
10248
  return schOrFunc;
10248
- let _sch = resolve3.call(this, root, ref);
10249
+ let _sch = resolve4.call(this, root, ref);
10249
10250
  if (_sch === void 0) {
10250
10251
  const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
10251
10252
  const { schemaId } = this.opts;
@@ -10272,7 +10273,7 @@ var require_compile2 = __commonJS({
10272
10273
  function sameSchemaEnv(s1, s2) {
10273
10274
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
10274
10275
  }
10275
- function resolve3(root, ref) {
10276
+ function resolve4(root, ref) {
10276
10277
  let sch;
10277
10278
  while (typeof (sch = this.refs[ref]) == "string")
10278
10279
  ref = sch;
@@ -17430,8 +17431,8 @@ var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
17430
17431
  var source_default = chalk;
17431
17432
 
17432
17433
  // src/commands/submit.ts
17433
- import { readFileSync, statSync as statSync2 } from "fs";
17434
- import { basename as basename3, resolve as resolve2 } from "path";
17434
+ import { readFileSync, statSync as statSync3 } from "fs";
17435
+ import { basename as basename3, resolve as resolve3 } from "path";
17435
17436
 
17436
17437
  // src/lib/scanner.ts
17437
17438
  init_esm_shims();
@@ -17867,7 +17868,7 @@ var retryifyAsync = (fn, options) => {
17867
17868
  throw error2;
17868
17869
  const delay = Math.round(interval * Math.random());
17869
17870
  if (delay > 0) {
17870
- const delayPromise = new Promise((resolve3) => setTimeout(resolve3, delay));
17871
+ const delayPromise = new Promise((resolve4) => setTimeout(resolve4, delay));
17871
17872
  return delayPromise.then(() => attempt.apply(void 0, args));
17872
17873
  } else {
17873
17874
  return attempt.apply(void 0, args);
@@ -18669,8 +18670,8 @@ var Conf = class {
18669
18670
  }
18670
18671
  try {
18671
18672
  const initializationVector = data.slice(0, 16);
18672
- const password = crypto2.pbkdf2Sync(this.#encryptionKey, initializationVector.toString(), 1e4, 32, "sha512");
18673
- const decipher = crypto2.createDecipheriv(encryptionAlgorithm, password, initializationVector);
18673
+ const password2 = crypto2.pbkdf2Sync(this.#encryptionKey, initializationVector.toString(), 1e4, 32, "sha512");
18674
+ const decipher = crypto2.createDecipheriv(encryptionAlgorithm, password2, initializationVector);
18674
18675
  const slice = data.slice(17);
18675
18676
  const dataUpdate = typeof slice === "string" ? stringToUint8Array(slice) : slice;
18676
18677
  return uint8ArrayToString(concatUint8Arrays([decipher.update(dataUpdate), decipher.final()]));
@@ -18714,8 +18715,8 @@ var Conf = class {
18714
18715
  let data = this._serialize(value);
18715
18716
  if (this.#encryptionKey) {
18716
18717
  const initializationVector = crypto2.randomBytes(16);
18717
- const password = crypto2.pbkdf2Sync(this.#encryptionKey, initializationVector.toString(), 1e4, 32, "sha512");
18718
- const cipher = crypto2.createCipheriv(encryptionAlgorithm, password, initializationVector);
18718
+ const password2 = crypto2.pbkdf2Sync(this.#encryptionKey, initializationVector.toString(), 1e4, 32, "sha512");
18719
+ const cipher = crypto2.createCipheriv(encryptionAlgorithm, password2, initializationVector);
18719
18720
  data = concatUint8Arrays([initializationVector, stringToUint8Array(":"), cipher.update(stringToUint8Array(data)), cipher.final()]);
18720
18721
  }
18721
18722
  if (process8.env.SNAP) {
@@ -18821,24 +18822,37 @@ var Conf = class {
18821
18822
  }
18822
18823
  };
18823
18824
 
18825
+ // src/lib/constants.ts
18826
+ init_esm_shims();
18827
+ var SUPABASE_URL = "https://cfqzdyktjhkalfrmcgmw.supabase.co";
18828
+ var SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImNmcXpkeWt0amhrYWxmcm1jZ213Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjkyNzM4MjYsImV4cCI6MjA4NDg0OTgyNn0.O1bPUNHw7kzpWecAyT4Pizh2ITRSal3PJsrUIkZY04o";
18829
+ var CALLBACK_PORT = 54321;
18830
+ var DEFAULT_API_URL = "https://preflightlaunch.com";
18831
+
18824
18832
  // src/lib/config.ts
18825
18833
  var config = new Conf({
18826
18834
  projectName: "preflight",
18827
18835
  schema: {
18828
18836
  accessToken: { type: "string" },
18829
18837
  refreshToken: { type: "string" },
18830
- apiUrl: { type: "string", default: "https://preflight.dev" },
18838
+ apiUrl: { type: "string", default: DEFAULT_API_URL },
18831
18839
  userId: { type: "string" },
18832
18840
  email: { type: "string" },
18833
18841
  hasRunBefore: { type: "boolean", default: false },
18834
18842
  lastScannedPath: { type: "string" }
18835
18843
  }
18836
18844
  });
18845
+ try {
18846
+ if (config.get("apiUrl") === "https://preflight.dev") {
18847
+ config.set("apiUrl", DEFAULT_API_URL);
18848
+ }
18849
+ } catch {
18850
+ }
18837
18851
  function getConfig() {
18838
18852
  return {
18839
18853
  accessToken: config.get("accessToken"),
18840
18854
  refreshToken: config.get("refreshToken"),
18841
- apiUrl: config.get("apiUrl") || "https://preflight.dev",
18855
+ apiUrl: config.get("apiUrl") || DEFAULT_API_URL,
18842
18856
  userId: config.get("userId"),
18843
18857
  email: config.get("email"),
18844
18858
  hasRunBefore: config.get("hasRunBefore") || false,
@@ -18899,11 +18913,11 @@ function __rest(s, e) {
18899
18913
  }
18900
18914
  function __awaiter(thisArg, _arguments, P3, generator) {
18901
18915
  function adopt(value) {
18902
- return value instanceof P3 ? value : new P3(function(resolve3) {
18903
- resolve3(value);
18916
+ return value instanceof P3 ? value : new P3(function(resolve4) {
18917
+ resolve4(value);
18904
18918
  });
18905
18919
  }
18906
- return new (P3 || (P3 = Promise))(function(resolve3, reject) {
18920
+ return new (P3 || (P3 = Promise))(function(resolve4, reject) {
18907
18921
  function fulfilled(value) {
18908
18922
  try {
18909
18923
  step(generator.next(value));
@@ -18919,7 +18933,7 @@ function __awaiter(thisArg, _arguments, P3, generator) {
18919
18933
  }
18920
18934
  }
18921
18935
  function step(result) {
18922
- result.done ? resolve3(result.value) : adopt(result.value).then(fulfilled, rejected);
18936
+ result.done ? resolve4(result.value) : adopt(result.value).then(fulfilled, rejected);
18923
18937
  }
18924
18938
  step((generator = generator.apply(thisArg, _arguments || [])).next());
18925
18939
  });
@@ -21472,15 +21486,15 @@ var RealtimeChannel = class _RealtimeChannel {
21472
21486
  }
21473
21487
  }
21474
21488
  } else {
21475
- return new Promise((resolve3) => {
21489
+ return new Promise((resolve4) => {
21476
21490
  var _a2, _b2, _c;
21477
21491
  const push = this._push(args.type, args, opts.timeout || this.timeout);
21478
21492
  if (args.type === "broadcast" && !((_c = (_b2 = (_a2 = this.params) === null || _a2 === void 0 ? void 0 : _a2.config) === null || _b2 === void 0 ? void 0 : _b2.broadcast) === null || _c === void 0 ? void 0 : _c.ack)) {
21479
- resolve3("ok");
21493
+ resolve4("ok");
21480
21494
  }
21481
- push.receive("ok", () => resolve3("ok"));
21482
- push.receive("error", () => resolve3("error"));
21483
- push.receive("timeout", () => resolve3("timed out"));
21495
+ push.receive("ok", () => resolve4("ok"));
21496
+ push.receive("error", () => resolve4("error"));
21497
+ push.receive("timeout", () => resolve4("timed out"));
21484
21498
  });
21485
21499
  }
21486
21500
  }
@@ -21508,16 +21522,16 @@ var RealtimeChannel = class _RealtimeChannel {
21508
21522
  };
21509
21523
  this.joinPush.destroy();
21510
21524
  let leavePush = null;
21511
- return new Promise((resolve3) => {
21525
+ return new Promise((resolve4) => {
21512
21526
  leavePush = new Push(this, CHANNEL_EVENTS.leave, {}, timeout);
21513
21527
  leavePush.receive("ok", () => {
21514
21528
  onClose();
21515
- resolve3("ok");
21529
+ resolve4("ok");
21516
21530
  }).receive("timeout", () => {
21517
21531
  onClose();
21518
- resolve3("timed out");
21532
+ resolve4("timed out");
21519
21533
  }).receive("error", () => {
21520
- resolve3("error");
21534
+ resolve4("error");
21521
21535
  });
21522
21536
  leavePush.send();
21523
21537
  if (!this._canPush()) {
@@ -23217,7 +23231,7 @@ var _getRequestParams = (method, options, parameters, body) => {
23217
23231
  return _objectSpread2(_objectSpread2({}, params), parameters);
23218
23232
  };
23219
23233
  async function _handleRequest(fetcher, method, url, options, parameters, body, namespace) {
23220
- return new Promise((resolve3, reject) => {
23234
+ return new Promise((resolve4, reject) => {
23221
23235
  fetcher(url, _getRequestParams(method, options, parameters, body)).then((result) => {
23222
23236
  if (!result.ok) throw result;
23223
23237
  if (options === null || options === void 0 ? void 0 : options.noResolveJson) return result;
@@ -23227,7 +23241,7 @@ async function _handleRequest(fetcher, method, url, options, parameters, body, n
23227
23241
  if (!contentType || !contentType.includes("application/json")) return {};
23228
23242
  }
23229
23243
  return result.json();
23230
- }).then((data) => resolve3(data)).catch((error2) => handleError(error2, reject, options, namespace));
23244
+ }).then((data) => resolve4(data)).catch((error2) => handleError(error2, reject, options, namespace));
23231
23245
  });
23232
23246
  }
23233
23247
  function createFetchApi(namespace = "storage") {
@@ -27724,7 +27738,7 @@ var GoTrueClient = class _GoTrueClient {
27724
27738
  try {
27725
27739
  let res;
27726
27740
  if ("email" in credentials) {
27727
- const { email, password, options } = credentials;
27741
+ const { email, password: password2, options } = credentials;
27728
27742
  let codeChallenge = null;
27729
27743
  let codeChallengeMethod = null;
27730
27744
  if (this.flowType === "pkce") {
@@ -27736,7 +27750,7 @@ var GoTrueClient = class _GoTrueClient {
27736
27750
  redirectTo: options === null || options === void 0 ? void 0 : options.emailRedirectTo,
27737
27751
  body: {
27738
27752
  email,
27739
- password,
27753
+ password: password2,
27740
27754
  data: (_a = options === null || options === void 0 ? void 0 : options.data) !== null && _a !== void 0 ? _a : {},
27741
27755
  gotrue_meta_security: { captcha_token: options === null || options === void 0 ? void 0 : options.captchaToken },
27742
27756
  code_challenge: codeChallenge,
@@ -27745,12 +27759,12 @@ var GoTrueClient = class _GoTrueClient {
27745
27759
  xform: _sessionResponse
27746
27760
  });
27747
27761
  } else if ("phone" in credentials) {
27748
- const { phone, password, options } = credentials;
27762
+ const { phone, password: password2, options } = credentials;
27749
27763
  res = await _request(this.fetch, "POST", `${this.url}/signup`, {
27750
27764
  headers: this.headers,
27751
27765
  body: {
27752
27766
  phone,
27753
- password,
27767
+ password: password2,
27754
27768
  data: (_b = options === null || options === void 0 ? void 0 : options.data) !== null && _b !== void 0 ? _b : {},
27755
27769
  channel: (_c = options === null || options === void 0 ? void 0 : options.channel) !== null && _c !== void 0 ? _c : "sms",
27756
27770
  gotrue_meta_security: { captcha_token: options === null || options === void 0 ? void 0 : options.captchaToken }
@@ -27792,23 +27806,23 @@ var GoTrueClient = class _GoTrueClient {
27792
27806
  try {
27793
27807
  let res;
27794
27808
  if ("email" in credentials) {
27795
- const { email, password, options } = credentials;
27809
+ const { email, password: password2, options } = credentials;
27796
27810
  res = await _request(this.fetch, "POST", `${this.url}/token?grant_type=password`, {
27797
27811
  headers: this.headers,
27798
27812
  body: {
27799
27813
  email,
27800
- password,
27814
+ password: password2,
27801
27815
  gotrue_meta_security: { captcha_token: options === null || options === void 0 ? void 0 : options.captchaToken }
27802
27816
  },
27803
27817
  xform: _sessionResponsePassword
27804
27818
  });
27805
27819
  } else if ("phone" in credentials) {
27806
- const { phone, password, options } = credentials;
27820
+ const { phone, password: password2, options } = credentials;
27807
27821
  res = await _request(this.fetch, "POST", `${this.url}/token?grant_type=password`, {
27808
27822
  headers: this.headers,
27809
27823
  body: {
27810
27824
  phone,
27811
- password,
27825
+ password: password2,
27812
27826
  gotrue_meta_security: { captcha_token: options === null || options === void 0 ? void 0 : options.captchaToken }
27813
27827
  },
27814
27828
  xform: _sessionResponsePassword
@@ -30308,13 +30322,6 @@ function shouldShowDeprecationWarning() {
30308
30322
  }
30309
30323
  if (shouldShowDeprecationWarning()) console.warn("\u26A0\uFE0F Node.js 18 and below are deprecated and will no longer be supported in future versions of @supabase/supabase-js. Please upgrade to Node.js 20 or later. For more information, visit: https://github.com/orgs/supabase/discussions/37217");
30310
30324
 
30311
- // src/lib/constants.ts
30312
- init_esm_shims();
30313
- var SUPABASE_URL = "https://cfqzdyktjhkalfrmcgmw.supabase.co";
30314
- var SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImNmcXpkeWt0amhrYWxmcm1jZ213Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjkyNzM4MjYsImV4cCI6MjA4NDg0OTgyNn0.O1bPUNHw7kzpWecAyT4Pizh2ITRSal3PJsrUIkZY04o";
30315
- var CALLBACK_PORT = 54321;
30316
- var DEFAULT_API_URL = "https://preflightlaunch.com";
30317
-
30318
30325
  // src/lib/api-client.ts
30319
30326
  var API_TIMEOUT_MS = 3e4;
30320
30327
  async function refreshSession() {
@@ -30379,10 +30386,14 @@ function decodeJwtPayload(token) {
30379
30386
  const decoded = Buffer.from(payload, "base64url").toString("utf-8");
30380
30387
  return JSON.parse(decoded);
30381
30388
  }
30382
- async function loginWithBrowser() {
30389
+ async function loginWithBrowser(mode = "login") {
30383
30390
  const { apiUrl } = getConfig();
30384
30391
  const baseUrl = apiUrl || DEFAULT_API_URL;
30385
- return new Promise((resolve3) => {
30392
+ if (mode === "signup") {
30393
+ await open_default(`${baseUrl}/auth/signup`);
30394
+ return null;
30395
+ }
30396
+ return new Promise((resolve4) => {
30386
30397
  const server = http.createServer(async (req, res) => {
30387
30398
  const url = new URL(req.url, `http://localhost:${CALLBACK_PORT}`);
30388
30399
  if (url.pathname === "/callback") {
@@ -30393,7 +30404,7 @@ async function loginWithBrowser() {
30393
30404
  res.writeHead(400, { "Content-Type": "text/html" });
30394
30405
  res.end(htmlPage("Login failed", errorMsg));
30395
30406
  server.close();
30396
- resolve3(null);
30407
+ resolve4(null);
30397
30408
  return;
30398
30409
  }
30399
30410
  try {
@@ -30405,12 +30416,12 @@ async function loginWithBrowser() {
30405
30416
  res.writeHead(200, { "Content-Type": "text/html" });
30406
30417
  res.end(htmlPage("Logged in to Preflight!", "You can close this tab and return to your terminal."));
30407
30418
  server.close();
30408
- resolve3({ email });
30419
+ resolve4({ email });
30409
30420
  } catch (err) {
30410
30421
  res.writeHead(500, { "Content-Type": "text/html" });
30411
30422
  res.end(htmlPage("Login failed", "Could not process authentication tokens."));
30412
30423
  server.close();
30413
- resolve3(null);
30424
+ resolve4(null);
30414
30425
  }
30415
30426
  } else {
30416
30427
  res.writeHead(404, { "Content-Type": "text/plain" });
@@ -30423,7 +30434,7 @@ async function loginWithBrowser() {
30423
30434
  } else {
30424
30435
  console.error(`Server error: ${err.message}`);
30425
30436
  }
30426
- resolve3(null);
30437
+ resolve4(null);
30427
30438
  });
30428
30439
  server.listen(CALLBACK_PORT, () => {
30429
30440
  const redirectTo = encodeURIComponent(`http://localhost:${CALLBACK_PORT}/callback`);
@@ -30432,7 +30443,7 @@ async function loginWithBrowser() {
30432
30443
  });
30433
30444
  setTimeout(() => {
30434
30445
  server.close();
30435
- resolve3(null);
30446
+ resolve4(null);
30436
30447
  }, 5 * 60 * 1e3);
30437
30448
  });
30438
30449
  }
@@ -32673,6 +32684,76 @@ function D(t2, e, s) {
32673
32684
  const i = t2 + e, r = Math.max(s.length - 1, 0), n = i < 0 ? r : i > r ? 0 : i;
32674
32685
  return s[n].disabled ? D(n, e < 0 ? -1 : 1, s) : n;
32675
32686
  }
32687
+ var Mt = class extends x {
32688
+ options;
32689
+ cursor = 0;
32690
+ get _value() {
32691
+ return this.options[this.cursor].value;
32692
+ }
32693
+ get _enabledOptions() {
32694
+ return this.options.filter((e) => e.disabled !== true);
32695
+ }
32696
+ toggleAll() {
32697
+ const e = this._enabledOptions, s = this.value !== void 0 && this.value.length === e.length;
32698
+ this.value = s ? [] : e.map((i) => i.value);
32699
+ }
32700
+ toggleInvert() {
32701
+ const e = this.value;
32702
+ if (!e) return;
32703
+ const s = this._enabledOptions.filter((i) => !e.includes(i.value));
32704
+ this.value = s.map((i) => i.value);
32705
+ }
32706
+ toggleValue() {
32707
+ this.value === void 0 && (this.value = []);
32708
+ const e = this.value.includes(this._value);
32709
+ this.value = e ? this.value.filter((s) => s !== this._value) : [...this.value, this._value];
32710
+ }
32711
+ constructor(e) {
32712
+ super(e, false), this.options = e.options, this.value = [...e.initialValues ?? []];
32713
+ const s = Math.max(this.options.findIndex(({ value: i }) => i === e.cursorAt), 0);
32714
+ this.cursor = this.options[s].disabled ? D(s, 1, this.options) : s, this.on("key", (i) => {
32715
+ i === "a" && this.toggleAll(), i === "i" && this.toggleInvert();
32716
+ }), this.on("cursor", (i) => {
32717
+ switch (i) {
32718
+ case "left":
32719
+ case "up":
32720
+ this.cursor = D(this.cursor, -1, this.options);
32721
+ break;
32722
+ case "down":
32723
+ case "right":
32724
+ this.cursor = D(this.cursor, 1, this.options);
32725
+ break;
32726
+ case "space":
32727
+ this.toggleValue();
32728
+ break;
32729
+ }
32730
+ });
32731
+ }
32732
+ };
32733
+ var Lt = class extends x {
32734
+ _mask = "\u2022";
32735
+ get cursor() {
32736
+ return this._cursor;
32737
+ }
32738
+ get masked() {
32739
+ return this.userInput.replaceAll(/./g, this._mask);
32740
+ }
32741
+ get userInputWithCursor() {
32742
+ if (this.state === "submit" || this.state === "cancel") return this.masked;
32743
+ const e = this.userInput;
32744
+ if (this.cursor >= e.length) return `${this.masked}${import_picocolors.default.inverse(import_picocolors.default.hidden("_"))}`;
32745
+ const s = this.masked, i = s.slice(0, this.cursor), r = s.slice(this.cursor);
32746
+ return `${i}${import_picocolors.default.inverse(r[0])}${r.slice(1)}`;
32747
+ }
32748
+ clear() {
32749
+ this._clearUserInput();
32750
+ }
32751
+ constructor({ mask: e, ...s }) {
32752
+ super(s), this._mask = e ?? "\u2022", this.on("userInput", (i) => {
32753
+ this._setValue(i);
32754
+ });
32755
+ }
32756
+ };
32676
32757
  var Wt = class extends x {
32677
32758
  options;
32678
32759
  cursor = 0;
@@ -33042,6 +33123,105 @@ ${import_picocolors2.default.gray(x2)} ${e}
33042
33123
 
33043
33124
  `);
33044
33125
  };
33126
+ var Q2 = (e, r) => e.split(`
33127
+ `).map((s) => r(s)).join(`
33128
+ `);
33129
+ var Lt2 = (e) => {
33130
+ const r = (i, n) => {
33131
+ const o = i.label ?? String(i.value);
33132
+ return n === "disabled" ? `${import_picocolors2.default.gray(z2)} ${Q2(o, (u) => import_picocolors2.default.strikethrough(import_picocolors2.default.gray(u)))}${i.hint ? ` ${import_picocolors2.default.dim(`(${i.hint ?? "disabled"})`)}` : ""}` : n === "active" ? `${import_picocolors2.default.cyan(te)} ${o}${i.hint ? ` ${import_picocolors2.default.dim(`(${i.hint})`)}` : ""}` : n === "selected" ? `${import_picocolors2.default.green(G2)} ${Q2(o, import_picocolors2.default.dim)}${i.hint ? ` ${import_picocolors2.default.dim(`(${i.hint})`)}` : ""}` : n === "cancelled" ? `${Q2(o, (u) => import_picocolors2.default.strikethrough(import_picocolors2.default.dim(u)))}` : n === "active-selected" ? `${import_picocolors2.default.green(G2)} ${o}${i.hint ? ` ${import_picocolors2.default.dim(`(${i.hint})`)}` : ""}` : n === "submitted" ? `${Q2(o, import_picocolors2.default.dim)}` : `${import_picocolors2.default.dim(z2)} ${Q2(o, import_picocolors2.default.dim)}`;
33133
+ }, s = e.required ?? true;
33134
+ return new Mt({ options: e.options, signal: e.signal, input: e.input, output: e.output, initialValues: e.initialValues, required: s, cursorAt: e.cursorAt, validate(i) {
33135
+ if (s && (i === void 0 || i.length === 0)) return `Please select at least one option.
33136
+ ${import_picocolors2.default.reset(import_picocolors2.default.dim(`Press ${import_picocolors2.default.gray(import_picocolors2.default.bgWhite(import_picocolors2.default.inverse(" space ")))} to select, ${import_picocolors2.default.gray(import_picocolors2.default.bgWhite(import_picocolors2.default.inverse(" enter ")))} to submit`))}`;
33137
+ }, render() {
33138
+ const i = Bt(e.output, e.message, `${Ee(this.state)} `, `${N2(this.state)} `), n = `${import_picocolors2.default.gray(h)}
33139
+ ${i}
33140
+ `, o = this.value ?? [], u = (l, a) => {
33141
+ if (l.disabled) return r(l, "disabled");
33142
+ const d = o.includes(l.value);
33143
+ return a && d ? r(l, "active-selected") : d ? r(l, "selected") : r(l, a ? "active" : "inactive");
33144
+ };
33145
+ switch (this.state) {
33146
+ case "submit": {
33147
+ const l = this.options.filter(({ value: d }) => o.includes(d)).map((d) => r(d, "submitted")).join(import_picocolors2.default.dim(", ")) || import_picocolors2.default.dim("none"), a = Bt(e.output, l, `${import_picocolors2.default.gray(h)} `);
33148
+ return `${n}${a}`;
33149
+ }
33150
+ case "cancel": {
33151
+ const l = this.options.filter(({ value: d }) => o.includes(d)).map((d) => r(d, "cancelled")).join(import_picocolors2.default.dim(", "));
33152
+ if (l.trim() === "") return `${n}${import_picocolors2.default.gray(h)}`;
33153
+ const a = Bt(e.output, l, `${import_picocolors2.default.gray(h)} `);
33154
+ return `${n}${a}
33155
+ ${import_picocolors2.default.gray(h)}`;
33156
+ }
33157
+ case "error": {
33158
+ const l = `${import_picocolors2.default.yellow(h)} `, a = this.error.split(`
33159
+ `).map((E, p) => p === 0 ? `${import_picocolors2.default.yellow(x2)} ${import_picocolors2.default.yellow(E)}` : ` ${E}`).join(`
33160
+ `), d = n.split(`
33161
+ `).length, g = a.split(`
33162
+ `).length + 1;
33163
+ return `${n}${l}${J2({ output: e.output, options: this.options, cursor: this.cursor, maxItems: e.maxItems, columnPadding: l.length, rowPadding: d + g, style: u }).join(`
33164
+ ${l}`)}
33165
+ ${a}
33166
+ `;
33167
+ }
33168
+ default: {
33169
+ const l = `${import_picocolors2.default.cyan(h)} `, a = n.split(`
33170
+ `).length;
33171
+ return `${n}${l}${J2({ output: e.output, options: this.options, cursor: this.cursor, maxItems: e.maxItems, columnPadding: l.length, rowPadding: a + 2, style: u }).join(`
33172
+ ${l}`)}
33173
+ ${import_picocolors2.default.cyan(x2)}
33174
+ `;
33175
+ }
33176
+ }
33177
+ } }).prompt();
33178
+ };
33179
+ var jt = (e) => import_picocolors2.default.dim(e);
33180
+ var Vt2 = (e, r, s) => {
33181
+ const i = { hard: true, trim: false }, n = q2(e, r, i).split(`
33182
+ `), o = n.reduce((a, d) => Math.max(M2(d), a), 0), u = n.map(s).reduce((a, d) => Math.max(M2(d), a), 0), l = r - (u - o);
33183
+ return q2(e, l, i);
33184
+ };
33185
+ var kt2 = (e = "", r = "", s) => {
33186
+ const i = s?.output ?? P2.stdout, n = (s?.withGuide ?? _.withGuide) !== false, o = s?.format ?? jt, u = ["", ...Vt2(e, rt(i) - 6, o).split(`
33187
+ `).map(o), ""], l = M2(r), a = Math.max(u.reduce((p, y2) => {
33188
+ const $ = M2(y2);
33189
+ return $ > p ? $ : p;
33190
+ }, 0), l) + 2, d = u.map((p) => `${import_picocolors2.default.gray(h)} ${p}${" ".repeat(a - M2(p))}${import_picocolors2.default.gray(h)}`).join(`
33191
+ `), g = n ? `${import_picocolors2.default.gray(h)}
33192
+ ` : "", E = n ? Ne : pe;
33193
+ i.write(`${g}${import_picocolors2.default.green(k2)} ${import_picocolors2.default.reset(r)} ${import_picocolors2.default.gray(se.repeat(Math.max(a - l - 1, 1)) + he)}
33194
+ ${d}
33195
+ ${import_picocolors2.default.gray(E + se.repeat(a + 2) + me)}
33196
+ `);
33197
+ };
33198
+ var Gt = (e) => new Lt({ validate: e.validate, mask: e.mask ?? Pe, signal: e.signal, input: e.input, output: e.output, render() {
33199
+ const r = `${import_picocolors2.default.gray(h)}
33200
+ ${N2(this.state)} ${e.message}
33201
+ `, s = this.userInputWithCursor, i = this.masked;
33202
+ switch (this.state) {
33203
+ case "error": {
33204
+ const n = i ? ` ${i}` : "";
33205
+ return e.clearOnError && this.clear(), `${r.trim()}
33206
+ ${import_picocolors2.default.yellow(h)}${n}
33207
+ ${import_picocolors2.default.yellow(x2)} ${import_picocolors2.default.yellow(this.error)}
33208
+ `;
33209
+ }
33210
+ case "submit": {
33211
+ const n = i ? ` ${import_picocolors2.default.dim(i)}` : "";
33212
+ return `${r}${import_picocolors2.default.gray(h)}${n}`;
33213
+ }
33214
+ case "cancel": {
33215
+ const n = i ? ` ${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(i))}` : "";
33216
+ return `${r}${import_picocolors2.default.gray(h)}${n}${i ? `
33217
+ ${import_picocolors2.default.gray(h)}` : ""}`;
33218
+ }
33219
+ default:
33220
+ return `${r}${import_picocolors2.default.cyan(h)} ${s}
33221
+ ${import_picocolors2.default.cyan(x2)}
33222
+ `;
33223
+ }
33224
+ } }).prompt();
33045
33225
  var Ut = import_picocolors2.default.magenta;
33046
33226
  var Ie = ({ indicator: e = "dots", onCancel: r, output: s = process.stdout, cancelMessage: i, errorMessage: n, frames: o = ee ? ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] : ["\u2022", "o", "O", "0"], delay: u = ee ? 80 : 120, signal: l, ...a } = {}) => {
33047
33227
  const d = ue();
@@ -33312,9 +33492,10 @@ function renderReportJson(report, items) {
33312
33492
 
33313
33493
  // src/lib/project-finder.ts
33314
33494
  init_esm_shims();
33315
- import { existsSync as existsSync2, readdirSync as readdirSync2 } from "fs";
33316
- import { join as join2, basename as basename2 } from "path";
33317
- import { homedir as homedir2 } from "os";
33495
+ import { existsSync as existsSync2, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
33496
+ import { execFileSync } from "child_process";
33497
+ import { join as join2, basename as basename2, dirname, resolve as resolve2 } from "path";
33498
+ import { homedir as homedir2, platform } from "os";
33318
33499
 
33319
33500
  // src/ui/interactive.ts
33320
33501
  init_esm_shims();
@@ -33324,24 +33505,24 @@ function intro(title) {
33324
33505
  R2.info(title);
33325
33506
  }
33326
33507
  }
33327
- function showTagline() {
33328
- R2.message(`${APP_TAGLINE}
33329
- Catch rejection reasons before Apple does.`);
33330
- }
33331
- function brandSplash() {
33332
- console.log();
33333
- console.log(brand(` ${APP_NAME.split("").join(" ")}`));
33334
- console.log();
33335
- console.log(subtext(` ${APP_TAGLINE}`));
33336
- console.log(subtext(` v${APP_VERSION}`));
33337
- console.log();
33338
- }
33339
33508
  function outro(message) {
33340
33509
  Wt2(message || "Done!");
33341
33510
  }
33342
33511
  function tip(message) {
33343
33512
  console.log();
33344
- console.log(subtext(` \u{1F4A1} Tip: ${message}`));
33513
+ console.log(subtext(` Tip: ${message}`));
33514
+ console.log();
33515
+ }
33516
+ function renderHeader(email, credits) {
33517
+ clearScreen();
33518
+ console.log();
33519
+ console.log(brand(` ${APP_NAME.split("").map((c) => c.toUpperCase()).join(" ")}`));
33520
+ if (email) {
33521
+ const creditDisplay = credits !== void 0 ? credits < 100 ? source_default.yellow(`${credits} credits`) : `${credits} credits` : "";
33522
+ console.log(subtext(` ${email}${creditDisplay ? ` \xB7 ${creditDisplay}` : ""}`));
33523
+ } else {
33524
+ console.log(subtext(` ${APP_TAGLINE}`));
33525
+ }
33345
33526
  console.log();
33346
33527
  }
33347
33528
  async function select(opts) {
@@ -33350,7 +33531,17 @@ async function select(opts) {
33350
33531
  options: opts.options
33351
33532
  });
33352
33533
  if (Ct(result)) {
33353
- Pt("Cancelled.");
33534
+ return null;
33535
+ }
33536
+ return result;
33537
+ }
33538
+ async function multiselect(opts) {
33539
+ const result = await Lt2({
33540
+ message: opts.message,
33541
+ options: opts.options,
33542
+ required: opts.required ?? false
33543
+ });
33544
+ if (Ct(result)) {
33354
33545
  return null;
33355
33546
  }
33356
33547
  return result;
@@ -33358,7 +33549,6 @@ async function select(opts) {
33358
33549
  async function confirm(message, initialValue = true) {
33359
33550
  const result = await Mt2({ message, initialValue });
33360
33551
  if (Ct(result)) {
33361
- Pt("Cancelled.");
33362
33552
  return null;
33363
33553
  }
33364
33554
  return result;
@@ -33366,7 +33556,13 @@ async function confirm(message, initialValue = true) {
33366
33556
  async function text(opts) {
33367
33557
  const result = await Qt(opts);
33368
33558
  if (Ct(result)) {
33369
- Pt("Cancelled.");
33559
+ return null;
33560
+ }
33561
+ return result;
33562
+ }
33563
+ async function password(opts) {
33564
+ const result = await Gt({ message: opts.message, validate: opts.validate });
33565
+ if (Ct(result)) {
33370
33566
  return null;
33371
33567
  }
33372
33568
  return result;
@@ -33375,6 +33571,14 @@ function spinner() {
33375
33571
  return Ie();
33376
33572
  }
33377
33573
  var log = R2;
33574
+ function clearScreen() {
33575
+ if (process.stdout.isTTY) {
33576
+ process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
33577
+ }
33578
+ }
33579
+ function note(message, title) {
33580
+ kt2(message, title);
33581
+ }
33378
33582
 
33379
33583
  // src/lib/project-finder.ts
33380
33584
  var SEARCH_DIRS = [
@@ -33386,6 +33590,45 @@ var SEARCH_DIRS = [
33386
33590
  "code",
33387
33591
  "dev"
33388
33592
  ];
33593
+ var NOISE_PATTERNS = [
33594
+ "/Pods/",
33595
+ "/DerivedData/",
33596
+ "/Carthage/",
33597
+ "/build/",
33598
+ "/.build/",
33599
+ "/SourcePackages/",
33600
+ "/Library/Developer/",
33601
+ "/.Trash/"
33602
+ ];
33603
+ function spotlightSearch() {
33604
+ if (platform() !== "darwin") return [];
33605
+ try {
33606
+ const output = execFileSync("mdfind", [
33607
+ 'kMDItemFSName == "*.xcodeproj" || kMDItemFSName == "*.xcworkspace"'
33608
+ ], { encoding: "utf-8", timeout: 5e3 });
33609
+ const lines = output.trim().split("\n").filter(Boolean);
33610
+ const seen = /* @__PURE__ */ new Set();
33611
+ const projects = [];
33612
+ for (const fullPath of lines) {
33613
+ if (NOISE_PATTERNS.some((pattern) => fullPath.includes(pattern))) continue;
33614
+ const projectDir = dirname(fullPath);
33615
+ if (seen.has(projectDir)) continue;
33616
+ seen.add(projectDir);
33617
+ const type = fullPath.endsWith(".xcworkspace") ? "xcworkspace" : "xcodeproj";
33618
+ const name = basename2(fullPath).replace(/\.(xcodeproj|xcworkspace)$/, "");
33619
+ let modifiedAt;
33620
+ try {
33621
+ modifiedAt = statSync2(fullPath).mtimeMs;
33622
+ } catch {
33623
+ }
33624
+ projects.push({ name, path: projectDir, type, fullPath, modifiedAt });
33625
+ }
33626
+ projects.sort((a, b) => (b.modifiedAt ?? 0) - (a.modifiedAt ?? 0));
33627
+ return projects;
33628
+ } catch {
33629
+ return [];
33630
+ }
33631
+ }
33389
33632
  function findXcodeProjects(extraDirs = []) {
33390
33633
  const home = homedir2();
33391
33634
  const projects = [];
@@ -33447,9 +33690,23 @@ function findProjectInDir(dir) {
33447
33690
  }
33448
33691
  return null;
33449
33692
  }
33693
+ function browseWithFinder() {
33694
+ if (platform() !== "darwin") return null;
33695
+ try {
33696
+ const result = execFileSync("osascript", [
33697
+ "-e",
33698
+ 'POSIX path of (choose folder with prompt "Select your Xcode project folder")'
33699
+ ], { encoding: "utf-8", timeout: 12e4 });
33700
+ const path5 = result.trim();
33701
+ return path5.endsWith("/") ? path5.slice(0, -1) : path5;
33702
+ } catch {
33703
+ return null;
33704
+ }
33705
+ }
33450
33706
  function buildProjectChoices(lastScannedPath) {
33451
33707
  const choices = [];
33452
33708
  const cwd = process.cwd();
33709
+ const addedPaths = /* @__PURE__ */ new Set();
33453
33710
  if (lastScannedPath && existsSync2(lastScannedPath)) {
33454
33711
  const proj = findProjectInDir(lastScannedPath);
33455
33712
  if (proj) {
@@ -33458,24 +33715,37 @@ function buildProjectChoices(lastScannedPath) {
33458
33715
  label: `${proj.name} (last scanned)`,
33459
33716
  hint: shortenPath(lastScannedPath)
33460
33717
  });
33718
+ addedPaths.add(lastScannedPath);
33461
33719
  }
33462
33720
  }
33463
33721
  const cwdProj = findProjectInDir(cwd);
33464
- if (cwdProj && cwd !== lastScannedPath) {
33722
+ if (cwdProj && !addedPaths.has(cwd)) {
33465
33723
  choices.push({
33466
33724
  value: cwd,
33467
33725
  label: cwdProj.name,
33468
- hint: `Current directory - ${shortenPath(cwd)}`
33726
+ hint: `Current directory`
33469
33727
  });
33728
+ addedPaths.add(cwd);
33729
+ }
33730
+ let found = spotlightSearch();
33731
+ if (found.length === 0) {
33732
+ found = findXcodeProjects();
33470
33733
  }
33471
- const found = findXcodeProjects();
33472
- for (const proj of found.slice(0, 5)) {
33473
- if (proj.path === lastScannedPath || proj.path === cwd) continue;
33734
+ for (const proj of found.slice(0, 8)) {
33735
+ if (addedPaths.has(proj.path)) continue;
33474
33736
  choices.push({
33475
33737
  value: proj.path,
33476
33738
  label: proj.name,
33477
33739
  hint: shortenPath(proj.path)
33478
33740
  });
33741
+ addedPaths.add(proj.path);
33742
+ }
33743
+ if (platform() === "darwin") {
33744
+ choices.push({
33745
+ value: "__finder__",
33746
+ label: "Open Finder to pick a folder",
33747
+ hint: "Browse with macOS file picker"
33748
+ });
33479
33749
  }
33480
33750
  choices.push({
33481
33751
  value: "__manual__",
@@ -33486,15 +33756,20 @@ function buildProjectChoices(lastScannedPath) {
33486
33756
  }
33487
33757
  async function interactiveProjectSelect() {
33488
33758
  const choices = buildProjectChoices(getLastScannedPath());
33489
- if (choices.length <= 1) {
33490
- log.info("No Xcode projects found in common locations.");
33491
- return promptForManualPath();
33759
+ const realProjects = choices.filter((c) => !c.value.startsWith("__"));
33760
+ if (realProjects.length === 0) {
33761
+ log.info("No Xcode projects found on your Mac.");
33492
33762
  }
33493
33763
  const selected = await select({
33494
33764
  message: "Where's your Xcode project?",
33495
33765
  options: choices
33496
33766
  });
33497
33767
  if (selected === null) return null;
33768
+ if (selected === "__finder__") {
33769
+ const finderPath = browseWithFinder();
33770
+ if (!finderPath) return null;
33771
+ return finderPath;
33772
+ }
33498
33773
  if (selected === "__manual__") return promptForManualPath();
33499
33774
  return selected;
33500
33775
  }
@@ -33504,6 +33779,8 @@ async function promptForManualPath() {
33504
33779
  placeholder: "./MyApp",
33505
33780
  validate: (val) => {
33506
33781
  if (!val?.trim()) return "Path is required";
33782
+ const resolved = resolve2(val.trim());
33783
+ if (!existsSync2(resolved)) return "Directory not found";
33507
33784
  }
33508
33785
  });
33509
33786
  }
@@ -33590,14 +33867,411 @@ async function promptLogin() {
33590
33867
  return result === "login";
33591
33868
  }
33592
33869
 
33870
+ // src/lib/submission-questions.ts
33871
+ init_esm_shims();
33872
+ var CATEGORIES = [
33873
+ "Business",
33874
+ "Developer Tools",
33875
+ "Education",
33876
+ "Finance",
33877
+ "Health & Fitness",
33878
+ "Lifestyle",
33879
+ "Productivity",
33880
+ "Social Networking",
33881
+ "Utilities"
33882
+ ];
33883
+ var AGE_RATING_CONTENT_TYPES = [
33884
+ { value: "cartoonViolence", label: "Cartoon violence", hint: "e.g., Tom & Jerry style" },
33885
+ { value: "realisticViolence", label: "Realistic violence", hint: "e.g., combat games" },
33886
+ { value: "prolongedViolence", label: "Graphic/intense violence", hint: "e.g., gore, torture" },
33887
+ { value: "sexualContent", label: "Sexual content or nudity", hint: "e.g., explicit images" },
33888
+ { value: "matureSuggestive", label: "Mature or suggestive themes", hint: "e.g., dating, romance" },
33889
+ { value: "profanity", label: "Swearing or crude humor", hint: "e.g., curse words" },
33890
+ { value: "alcoholDrugs", label: "Alcohol, tobacco, or drugs", hint: "e.g., drinking scenes" },
33891
+ { value: "gamblingSimulated", label: "Gambling (no real money)", hint: "e.g., casino games with fake chips" },
33892
+ { value: "horrorFear", label: "Horror or scary content", hint: "e.g., jump scares" },
33893
+ { value: "medicalTreatment", label: "Medical or health advice", hint: "e.g., treatment suggestions" },
33894
+ { value: "gamblingContests", label: "Real money gambling or paid contests", hint: "e.g., betting, fantasy sports" }
33895
+ ];
33896
+ var PRIVACY_DATA_TYPES = [
33897
+ { value: "contact", label: "Contact Info", hint: "Name, email, phone, address" },
33898
+ { value: "health", label: "Health & Fitness", hint: "Workouts, steps, medical info" },
33899
+ { value: "financial", label: "Financial Info", hint: "Credit cards, bank details" },
33900
+ { value: "location", label: "Location", hint: "GPS, location data" },
33901
+ { value: "sensitive", label: "Sensitive Info", hint: "Race, religion, politics" },
33902
+ { value: "contacts", label: "Contacts", hint: "Phone contacts, address book" },
33903
+ { value: "content", label: "User Content", hint: "Photos, videos, posts" },
33904
+ { value: "browsing", label: "Browsing History", hint: "Websites visited" },
33905
+ { value: "search", label: "Search History", hint: "In-app searches" },
33906
+ { value: "identifiers", label: "Identifiers", hint: "User ID, device ID" },
33907
+ { value: "purchases", label: "Purchases", hint: "Purchase history" },
33908
+ { value: "usage", label: "Usage Data", hint: "Button taps, feature usage" },
33909
+ { value: "diagnostics", label: "Diagnostics", hint: "Crash reports (Firebase, Sentry)" },
33910
+ { value: "other", label: "Other Data", hint: "Anything else not listed" }
33911
+ ];
33912
+ var FEATURE_ITEMS = [
33913
+ { value: "ugc", label: "User Posts & Uploads", hint: "Comments, photos, sharing" },
33914
+ { value: "login", label: "Account / Login", hint: "Sign up or log in required" },
33915
+ { value: "iap", label: "Pay to Unlock Features", hint: "One-time purchases" },
33916
+ { value: "subscriptions", label: "Subscription / Recurring Payment", hint: "Weekly, monthly, yearly" },
33917
+ { value: "ads", label: "Ads in Your App", hint: "Banner, video, or sponsored" },
33918
+ { value: "thirdPartyLogin", label: "Sign in with Apple / Google", hint: "Social login" },
33919
+ { value: "aiContent", label: "AI-Generated Content", hint: "ChatGPT, DALL-E, etc." },
33920
+ { value: "healthClaims", label: "Health / Medical Advice", hint: "Diagnosis, treatment" },
33921
+ { value: "crypto", label: "Crypto / NFTs", hint: "Buy, sell, trade" }
33922
+ ];
33923
+ function calculateAgeRating(answers) {
33924
+ if (answers.prolongedViolence === 2 || answers.sexualContent === 2 || answers.gamblingSimulated === 2 || answers.gamblingContests > 0) {
33925
+ return "17+";
33926
+ }
33927
+ if (answers.realisticViolence > 0 || answers.sexualContent > 0 || answers.matureSuggestive === 2 || answers.alcoholDrugs === 2 || answers.gamblingSimulated > 0) {
33928
+ return "12+";
33929
+ }
33930
+ if (answers.cartoonViolence === 2 || answers.matureSuggestive > 0 || answers.profanity === 2 || answers.horrorFear === 2) {
33931
+ return "9+";
33932
+ }
33933
+ return "4+";
33934
+ }
33935
+ async function collectAppDetails(projectName) {
33936
+ const skipGate = await select({
33937
+ message: "App Details (you can always add these later on the web)",
33938
+ options: [
33939
+ { value: "fill", label: "Fill in now", hint: "Name, description, keywords, category" },
33940
+ { value: "skip", label: "Skip for now", hint: "Just use the project name" }
33941
+ ]
33942
+ });
33943
+ if (skipGate === null) return null;
33944
+ if (skipGate === "skip") {
33945
+ return {
33946
+ appName: projectName,
33947
+ signInRequired: false
33948
+ };
33949
+ }
33950
+ const appName = await text({
33951
+ message: "App Name",
33952
+ placeholder: projectName,
33953
+ defaultValue: projectName,
33954
+ validate: (val) => {
33955
+ if (!val?.trim()) return "App name is required";
33956
+ }
33957
+ });
33958
+ if (appName === null) return null;
33959
+ const description = await text({
33960
+ message: "Description (press Enter to skip)",
33961
+ placeholder: "Describe your app as it appears in the App Store"
33962
+ });
33963
+ if (description === null) return null;
33964
+ const keywords = await text({
33965
+ message: "Keywords (press Enter to skip)",
33966
+ placeholder: "Comma-separated, 100 chars max",
33967
+ validate: (val) => {
33968
+ if (val && val.length > 100) return "Keywords must be 100 characters or less";
33969
+ }
33970
+ });
33971
+ if (keywords === null) return null;
33972
+ const promotionalText = await text({
33973
+ message: "Promotional Text (press Enter to skip)",
33974
+ placeholder: "Short promotional text, 170 chars max",
33975
+ validate: (val) => {
33976
+ if (val && val.length > 170) return "Promotional text must be 170 characters or less";
33977
+ }
33978
+ });
33979
+ if (promotionalText === null) return null;
33980
+ const categoryOptions = [
33981
+ { value: "__skip__", label: "Skip", hint: "Choose later" },
33982
+ ...CATEGORIES.map((c) => ({ value: c, label: c }))
33983
+ ];
33984
+ const category = await select({
33985
+ message: "Primary Category",
33986
+ options: categoryOptions
33987
+ });
33988
+ if (category === null) return null;
33989
+ const supportUrl = await text({
33990
+ message: "Support URL (press Enter to skip)",
33991
+ placeholder: "https://example.com/support"
33992
+ });
33993
+ if (supportUrl === null) return null;
33994
+ const marketingUrl = await text({
33995
+ message: "Marketing URL (press Enter to skip)",
33996
+ placeholder: "https://example.com"
33997
+ });
33998
+ if (marketingUrl === null) return null;
33999
+ const signInRequired = await confirm("Does your app require sign-in for review?", false);
34000
+ if (signInRequired === null) return null;
34001
+ let demoUsername;
34002
+ let demoPassword;
34003
+ if (signInRequired) {
34004
+ const email = await text({
34005
+ message: "Demo Email",
34006
+ placeholder: "test@example.com",
34007
+ validate: (val) => {
34008
+ if (!val?.trim()) return "Demo email is required when sign-in is required";
34009
+ }
34010
+ });
34011
+ if (email === null) return null;
34012
+ demoUsername = email;
34013
+ const pass = await password({
34014
+ message: "Demo Password",
34015
+ validate: (val) => {
34016
+ if (!val?.trim()) return "Demo password is required when sign-in is required";
34017
+ }
34018
+ });
34019
+ if (pass === null) return null;
34020
+ demoPassword = pass;
34021
+ }
34022
+ return {
34023
+ appName: appName.trim(),
34024
+ description: description?.trim() || void 0,
34025
+ keywords: keywords?.trim() || void 0,
34026
+ category: category === "__skip__" ? void 0 : category,
34027
+ supportUrl: supportUrl?.trim() || void 0,
34028
+ promotionalText: promotionalText?.trim() || void 0,
34029
+ marketingUrl: marketingUrl?.trim() || void 0,
34030
+ signInRequired,
34031
+ demoUsername,
34032
+ demoPassword
34033
+ };
34034
+ }
34035
+ async function collectAgeRating() {
34036
+ log.step(subtext("Step 1 of 3: Age Rating"));
34037
+ const defaultAnswers = {
34038
+ cartoonViolence: 0,
34039
+ realisticViolence: 0,
34040
+ prolongedViolence: 0,
34041
+ sexualContent: 0,
34042
+ matureSuggestive: 0,
34043
+ profanity: 0,
34044
+ alcoholDrugs: 0,
34045
+ gamblingSimulated: 0,
34046
+ horrorFear: 0,
34047
+ medicalTreatment: 0,
34048
+ gamblingContests: 0,
34049
+ unrestrictedWebAccess: false,
34050
+ madeForKids: false
34051
+ };
34052
+ const hasMatureContent = await confirm(
34053
+ "Does your app contain any mature content? (violence, sexual content, drugs, gambling, horror)",
34054
+ false
34055
+ );
34056
+ if (hasMatureContent === null) return null;
34057
+ if (!hasMatureContent) {
34058
+ const rating = calculateAgeRating(defaultAnswers);
34059
+ log.success(`Age Rating: ${rating} (no mature content)`);
34060
+ const looksRight = await confirm("Does that look right?", true);
34061
+ if (looksRight === null) return null;
34062
+ if (!looksRight) {
34063
+ return collectAgeRatingDetailed(defaultAnswers);
34064
+ }
34065
+ return { answers: defaultAnswers, rating };
34066
+ }
34067
+ return collectAgeRatingDetailed(defaultAnswers);
34068
+ }
34069
+ async function collectAgeRatingDetailed(answers) {
34070
+ const selectedTypes = await multiselect({
34071
+ message: "Which types of content does your app contain? (Space to select, Enter to confirm)",
34072
+ options: AGE_RATING_CONTENT_TYPES
34073
+ });
34074
+ if (selectedTypes === null) return null;
34075
+ const updatedAnswers = { ...answers };
34076
+ for (const typeKey of selectedTypes) {
34077
+ const typeInfo = AGE_RATING_CONTENT_TYPES.find((t2) => t2.value === typeKey);
34078
+ if (!typeInfo) continue;
34079
+ const severity = await select({
34080
+ message: `${typeInfo.label}: how much?`,
34081
+ options: [
34082
+ { value: "1", label: "A little", hint: "Minor/occasional" },
34083
+ { value: "2", label: "A lot", hint: "Frequent/prominent" }
34084
+ ]
34085
+ });
34086
+ if (severity === null) return null;
34087
+ updatedAnswers[typeKey] = parseInt(severity);
34088
+ }
34089
+ const webAccess = await confirm("Can users browse any website in your app? (e.g., in-app browser)", false);
34090
+ if (webAccess === null) return null;
34091
+ updatedAnswers.unrestrictedWebAccess = webAccess;
34092
+ const madeForKids = await confirm("Is this app designed specifically for kids under 13?", false);
34093
+ if (madeForKids === null) return null;
34094
+ updatedAnswers.madeForKids = madeForKids;
34095
+ const rating = calculateAgeRating(updatedAnswers);
34096
+ log.success(`Age Rating: ${rating}`);
34097
+ return { answers: updatedAnswers, rating };
34098
+ }
34099
+ async function collectPrivacyData() {
34100
+ log.step(subtext("Step 2 of 3: Privacy & Data"));
34101
+ const collectsData = await confirm("Does your app collect any user data?", false);
34102
+ if (collectsData === null) return null;
34103
+ const emptyData = Object.fromEntries(
34104
+ PRIVACY_DATA_TYPES.map((t2) => [t2.value, { collected: false, linked: false }])
34105
+ );
34106
+ if (!collectsData) {
34107
+ return { data: emptyData, tracking: false };
34108
+ }
34109
+ const collectedTypes = await multiselect({
34110
+ message: "What data does your app collect? (Space to select, Enter to confirm)",
34111
+ options: PRIVACY_DATA_TYPES
34112
+ });
34113
+ if (collectedTypes === null) return null;
34114
+ const data = { ...emptyData };
34115
+ for (const typeKey of collectedTypes) {
34116
+ data[typeKey] = { collected: true, linked: false };
34117
+ const linked = await confirm(
34118
+ `Can you tie ${PRIVACY_DATA_TYPES.find((t2) => t2.value === typeKey)?.label || typeKey} to a specific person? (e.g., through their account)`,
34119
+ false
34120
+ );
34121
+ if (linked === null) return null;
34122
+ data[typeKey].linked = linked;
34123
+ }
34124
+ const tracking = await confirm(
34125
+ "Does your app use data to track users across other companies' apps and websites?",
34126
+ false
34127
+ );
34128
+ if (tracking === null) return null;
34129
+ return { data, tracking };
34130
+ }
34131
+ async function collectFeatureChecklist() {
34132
+ log.step(subtext("Step 3 of 3: Features"));
34133
+ const selectedFeatures = await multiselect({
34134
+ message: "Which features does your app include? (Space to select, Enter to confirm)",
34135
+ options: FEATURE_ITEMS
34136
+ });
34137
+ if (selectedFeatures === null) return null;
34138
+ const checklist = {
34139
+ ugc: selectedFeatures.includes("ugc"),
34140
+ login: selectedFeatures.includes("login"),
34141
+ iap: selectedFeatures.includes("iap"),
34142
+ subscriptions: selectedFeatures.includes("subscriptions"),
34143
+ ads: selectedFeatures.includes("ads"),
34144
+ thirdPartyLogin: selectedFeatures.includes("thirdPartyLogin"),
34145
+ aiContent: selectedFeatures.includes("aiContent"),
34146
+ healthClaims: selectedFeatures.includes("healthClaims"),
34147
+ crypto: selectedFeatures.includes("crypto")
34148
+ };
34149
+ if (checklist.login) {
34150
+ const hasAccountDeletion = await confirm(
34151
+ "Does your app have an account deletion button? (Apple requires this!)",
34152
+ false
34153
+ );
34154
+ if (hasAccountDeletion === null) return null;
34155
+ checklist.accountDeletion = hasAccountDeletion;
34156
+ }
34157
+ if (checklist.iap || checklist.subscriptions) {
34158
+ const hasRestorePurchases = await confirm(
34159
+ 'Does your app have a "Restore Purchases" button? (Apple requires this!)',
34160
+ false
34161
+ );
34162
+ if (hasRestorePurchases === null) return null;
34163
+ checklist.restorePurchases = hasRestorePurchases;
34164
+ }
34165
+ return checklist;
34166
+ }
34167
+ async function collectCompliance() {
34168
+ const ageResult = await collectAgeRating();
34169
+ if (ageResult === null) return null;
34170
+ const privacyResult = await collectPrivacyData();
34171
+ if (privacyResult === null) return null;
34172
+ const checklistResult = await collectFeatureChecklist();
34173
+ if (checklistResult === null) return null;
34174
+ return {
34175
+ ageRatingAnswers: ageResult.answers,
34176
+ ageRating: ageResult.rating,
34177
+ privacyDeclarations: privacyResult,
34178
+ checklist: checklistResult
34179
+ };
34180
+ }
34181
+ function formatComplianceForApi(compliance) {
34182
+ return {
34183
+ age_rating: compliance.ageRatingAnswers,
34184
+ age_rating_result: compliance.ageRating,
34185
+ privacy_declarations: {
34186
+ data: compliance.privacyDeclarations.data,
34187
+ tracking: compliance.privacyDeclarations.tracking
34188
+ },
34189
+ checklist: compliance.checklist
34190
+ };
34191
+ }
34192
+ function formatComplianceSummary(compliance) {
34193
+ const lines = [];
34194
+ lines.push(` Age Rating: ${compliance.ageRating}`);
34195
+ const collectedTypes = Object.entries(compliance.privacyDeclarations.data).filter(([_2, v]) => v.collected).map(([k3, _2]) => {
34196
+ const typeInfo = PRIVACY_DATA_TYPES.find((t2) => t2.value === k3);
34197
+ return typeInfo?.label || k3;
34198
+ });
34199
+ if (collectedTypes.length > 0) {
34200
+ lines.push(` Privacy: ${collectedTypes.join(", ")}`);
34201
+ } else {
34202
+ lines.push(` Privacy: No data collected`);
34203
+ }
34204
+ const enabledFeatures = Object.entries(compliance.checklist).filter(([_2, v]) => v === true).map(([k3, _2]) => {
34205
+ const featureInfo = FEATURE_ITEMS.find((f) => f.value === k3);
34206
+ return featureInfo?.label || k3;
34207
+ });
34208
+ if (enabledFeatures.length > 0) {
34209
+ lines.push(` Features: ${enabledFeatures.join(", ")}`);
34210
+ } else {
34211
+ lines.push(` Features: None selected`);
34212
+ }
34213
+ return lines;
34214
+ }
34215
+
33593
34216
  // src/commands/submit.ts
33594
- async function submitCommand(path5, options = {}) {
34217
+ async function openUrl(url) {
34218
+ try {
34219
+ const open = (await import("./open-A77P4RC4.js")).default;
34220
+ await open(url);
34221
+ } catch {
34222
+ console.log(subtext(` Visit: ${url}`));
34223
+ }
34224
+ }
34225
+ async function fetchCredits() {
34226
+ try {
34227
+ const res = await apiRequest("/api/credits");
34228
+ if (!res.ok) return null;
34229
+ const data = await res.json();
34230
+ return data.credits ?? null;
34231
+ } catch {
34232
+ return null;
34233
+ }
34234
+ }
34235
+ async function creditPreCheck() {
34236
+ const credits = await fetchCredits();
34237
+ if (credits === null) {
34238
+ log.warning("Could not verify credit balance. Proceeding anyway.");
34239
+ return true;
34240
+ }
34241
+ if (credits >= 100) return true;
34242
+ log.warning(`You need 100 credits for a review. You currently have ${credits}.`);
34243
+ console.log();
34244
+ const wantsBuy = await confirm("Would you like to buy more credits?");
34245
+ if (wantsBuy === null || !wantsBuy) return false;
34246
+ await openUrl("https://preflightlaunch.com/pricing");
34247
+ log.info("Opened pricing page in browser.");
34248
+ console.log();
34249
+ log.info(subtext("Waiting for credits... Press Enter to check now, or Esc to cancel."));
34250
+ let attempts = 0;
34251
+ const maxAttempts = 60;
34252
+ while (attempts < maxAttempts) {
34253
+ await new Promise((r) => setTimeout(r, 1e4));
34254
+ attempts++;
34255
+ const newCredits = await fetchCredits();
34256
+ if (newCredits !== null && newCredits >= 100) {
34257
+ log.success(`Credits updated! You now have ${newCredits} credits.`);
34258
+ return true;
34259
+ }
34260
+ }
34261
+ log.warning("Still waiting for credits. You can try again later.");
34262
+ return false;
34263
+ }
34264
+ async function submitCommand(path5, options = {}, fromMenu = false) {
33595
34265
  if (!isLoggedIn()) {
34266
+ if (fromMenu) {
34267
+ log.error("Not logged in.");
34268
+ return;
34269
+ }
33596
34270
  const wantsLogin = await promptLogin();
33597
34271
  if (wantsLogin) {
33598
34272
  const s = spinner();
33599
34273
  s.start("Opening browser...");
33600
- const result = await loginWithBrowser();
34274
+ const result = await loginWithBrowser("login");
33601
34275
  if (result) {
33602
34276
  s.stop(`Logged in as ${result.email}`);
33603
34277
  } else {
@@ -33610,18 +34284,23 @@ async function submitCommand(path5, options = {}) {
33610
34284
  return;
33611
34285
  }
33612
34286
  }
34287
+ if (fromMenu) {
34288
+ const hasCredits = await creditPreCheck();
34289
+ if (!hasCredits) return;
34290
+ }
33613
34291
  if (!path5) {
33614
34292
  const resolvedPath = await interactiveProjectSelect();
33615
34293
  if (!resolvedPath) return;
33616
34294
  path5 = resolvedPath;
33617
34295
  }
33618
- const dir = resolve2(path5);
34296
+ const dir = resolve3(path5);
33619
34297
  setLastScannedPath(dir);
33620
34298
  const detected = scanProject(dir);
33621
- const appName = options.appName || detected.projectName || "Unknown App";
33622
- if (options.plist) detected.infoPlist = resolve2(options.plist);
33623
- if (options.manifest) detected.privacyManifest = resolve2(options.manifest);
33624
- if (options.ipa) detected.ipa = resolve2(options.ipa);
34299
+ const projectName = detected.projectName || "Unknown App";
34300
+ let appName = options.appName || projectName;
34301
+ if (options.plist) detected.infoPlist = resolve3(options.plist);
34302
+ if (options.manifest) detected.privacyManifest = resolve3(options.manifest);
34303
+ if (options.ipa) detected.ipa = resolve3(options.ipa);
33625
34304
  const filesToUpload = [];
33626
34305
  if (detected.infoPlist) {
33627
34306
  filesToUpload.push({ type: "plist", filename: "Info.plist", path: detected.infoPlist });
@@ -33641,39 +34320,94 @@ async function submitCommand(path5, options = {}) {
33641
34320
  });
33642
34321
  }
33643
34322
  if (filesToUpload.length === 0) {
33644
- log.warning("No files to upload. Use --plist, --manifest, --ipa, or --screenshots flags.");
34323
+ log.warning("No files to upload. Make sure you're pointing to an Xcode project directory.");
34324
+ if (fromMenu) return;
34325
+ log.info(subtext("Use --plist, --manifest, --ipa, or --screenshots flags to specify files manually."));
33645
34326
  return;
33646
34327
  }
33647
- intro(`Submit ${appName} for analysis`);
33648
- const fileLines = filesToUpload.map((f) => {
33649
- const size = getFileSize(f.path);
33650
- const icon = f.type === "screenshot" ? icons.image : icons.file;
33651
- return ` ${icon} ${f.filename} ${subtext(`(${formatBytes(size)})`)}`;
33652
- });
33653
- log.message(source_default.bold("Files to upload:") + "\n" + fileLines.join("\n"));
33654
- const shouldContinue = await confirm("This will use 1 credit. Continue?");
33655
- if (shouldContinue === null) return;
33656
- if (!shouldContinue) {
33657
- outro("Submission cancelled.");
33658
- return;
34328
+ let appDetails = null;
34329
+ let compliance = null;
34330
+ if (fromMenu) {
34331
+ const reviewType = await select({
34332
+ message: "What would you like to include in your review?",
34333
+ options: [
34334
+ { value: "quick", label: "Quick review (just analyze my project files)", hint: "Fastest option" },
34335
+ { value: "full", label: "Full review (add app details + compliance info)", hint: "More thorough" }
34336
+ ]
34337
+ });
34338
+ if (reviewType === null) return;
34339
+ if (reviewType === "full") {
34340
+ appDetails = await collectAppDetails(projectName);
34341
+ if (appDetails === null) return;
34342
+ appName = appDetails.appName;
34343
+ compliance = await collectCompliance();
34344
+ if (compliance === null) return;
34345
+ }
34346
+ }
34347
+ if (fromMenu) {
34348
+ console.log();
34349
+ note(buildSummary(appName, dir, filesToUpload, compliance), "Review Summary");
34350
+ const action = await select({
34351
+ message: `Submit review? (100 credits)`,
34352
+ options: [
34353
+ { value: "submit", label: "Submit review", hint: "100 credits will be deducted" },
34354
+ { value: "cancel", label: "Cancel", hint: "Back to menu" }
34355
+ ]
34356
+ });
34357
+ if (action === null || action === "cancel") return;
34358
+ } else {
34359
+ if (!fromMenu) {
34360
+ intro(`Submit ${appName} for analysis`);
34361
+ const fileLines = filesToUpload.map((f) => {
34362
+ const size = getFileSize(f.path);
34363
+ const icon = f.type === "screenshot" ? icons.image : icons.file;
34364
+ return ` ${icon} ${f.filename} ${subtext(`(${formatBytes(size)})`)}`;
34365
+ });
34366
+ log.message(source_default.bold("Files to upload:") + "\n" + fileLines.join("\n"));
34367
+ const shouldContinue = await confirm("This will use 100 credits. Continue?");
34368
+ if (shouldContinue === null || !shouldContinue) {
34369
+ outro("Submission cancelled.");
34370
+ return;
34371
+ }
34372
+ }
33659
34373
  }
34374
+ log.info(subtext("Reviews usually take 1-3 minutes."));
34375
+ console.log();
33660
34376
  const spinner2 = createSpinner("Creating submission...");
33661
34377
  spinner2.start();
34378
+ let activeSpinner = spinner2;
33662
34379
  try {
34380
+ const submissionBody = { app_name: appName };
34381
+ if (appDetails) {
34382
+ if (appDetails.description) submissionBody.description = appDetails.description;
34383
+ if (appDetails.keywords) submissionBody.keywords = appDetails.keywords;
34384
+ if (appDetails.category) submissionBody.category = appDetails.category;
34385
+ if (appDetails.supportUrl) submissionBody.support_url = appDetails.supportUrl;
34386
+ if (appDetails.promotionalText) submissionBody.promotional_text = appDetails.promotionalText;
34387
+ if (appDetails.marketingUrl) submissionBody.marketing_url = appDetails.marketingUrl;
34388
+ submissionBody.sign_in_required = appDetails.signInRequired;
34389
+ if (appDetails.demoUsername) submissionBody.demo_username = appDetails.demoUsername;
34390
+ if (appDetails.demoPassword) submissionBody.demo_password = appDetails.demoPassword;
34391
+ }
34392
+ if (compliance) {
34393
+ Object.assign(submissionBody, formatComplianceForApi(compliance));
34394
+ }
33663
34395
  const createRes = await apiRequest("/api/submissions", {
33664
34396
  method: "POST",
33665
- body: JSON.stringify({ app_name: appName })
34397
+ body: JSON.stringify(submissionBody)
33666
34398
  });
33667
34399
  const createData = await createRes.json();
33668
34400
  if (!createRes.ok) {
33669
34401
  spinner2.stop();
33670
34402
  log.error(createData.message || "Failed to create submission");
33671
- process.exit(1);
34403
+ if (!fromMenu) process.exitCode = 1;
34404
+ return;
33672
34405
  }
33673
34406
  const submissionId = createData.submissionId;
33674
34407
  spinner2.succeed("Submission created");
33675
34408
  const uploadSpinner = createSpinner("Getting upload URLs...");
33676
34409
  uploadSpinner.start();
34410
+ activeSpinner = uploadSpinner;
33677
34411
  const urlsRes = await apiRequest(`/api/submissions/${submissionId}/upload-urls`, {
33678
34412
  method: "POST",
33679
34413
  body: JSON.stringify({
@@ -33688,7 +34422,8 @@ async function submitCommand(path5, options = {}) {
33688
34422
  if (!urlsRes.ok) {
33689
34423
  uploadSpinner.stop();
33690
34424
  log.error(urlsData.message || "Failed to get upload URLs");
33691
- process.exit(1);
34425
+ if (!fromMenu) process.exitCode = 1;
34426
+ return;
33692
34427
  }
33693
34428
  for (let i = 0; i < urlsData.urls.length; i++) {
33694
34429
  const urlInfo = urlsData.urls[i];
@@ -33704,35 +34439,60 @@ async function submitCommand(path5, options = {}) {
33704
34439
  if (!uploadRes.ok) {
33705
34440
  uploadSpinner.stop();
33706
34441
  log.error(`Failed to upload ${fileInfo.filename}: HTTP ${uploadRes.status} ${uploadRes.statusText}`);
33707
- process.exit(1);
34442
+ if (!fromMenu) process.exitCode = 1;
34443
+ return;
33708
34444
  }
33709
34445
  }
33710
34446
  uploadSpinner.succeed("Files uploaded");
33711
34447
  const analyzeSpinner = createSpinner("Starting analysis...");
33712
34448
  analyzeSpinner.start();
33713
- const finalizeRes = await apiRequest(`/api/submissions/${submissionId}/finalize`, {
33714
- method: "POST",
33715
- body: JSON.stringify({
33716
- files: filesToUpload.map((f) => ({
33717
- type: f.type,
33718
- index: f.index
33719
- }))
33720
- })
33721
- });
33722
- const finalizeData = await finalizeRes.json();
33723
- if (!finalizeRes.ok) {
33724
- analyzeSpinner.stop();
33725
- if (finalizeRes.status === 402) {
33726
- log.error(`Insufficient credits. Need ${finalizeData.required}, have ${finalizeData.credits}.`);
33727
- console.log(subtext(" Purchase credits at https://preflightlaunch.com/pricing"));
34449
+ activeSpinner = analyzeSpinner;
34450
+ const finalizePayload = {
34451
+ files: filesToUpload.map((f) => ({
34452
+ type: f.type,
34453
+ index: f.index
34454
+ }))
34455
+ };
34456
+ let finalizeSuccess = false;
34457
+ let maxFinalizeRetries = 3;
34458
+ while (!finalizeSuccess && maxFinalizeRetries > 0) {
34459
+ const finalizeRes = await apiRequest(`/api/submissions/${submissionId}/finalize`, {
34460
+ method: "POST",
34461
+ body: JSON.stringify(finalizePayload)
34462
+ });
34463
+ const finalizeData = await finalizeRes.json();
34464
+ if (finalizeRes.ok) {
34465
+ finalizeSuccess = true;
34466
+ } else if (finalizeRes.status === 402) {
34467
+ analyzeSpinner.stop();
34468
+ log.warning(`Not enough credits. Need ${finalizeData.required ?? 100}, have ${finalizeData.credits ?? 0}.`);
34469
+ console.log();
34470
+ const wantsBuy = await confirm("Would you like to buy more credits?");
34471
+ if (wantsBuy === null || !wantsBuy) return;
34472
+ await openUrl("https://preflightlaunch.com/pricing");
34473
+ log.info("Opened pricing page. Press Enter when you've purchased credits.");
34474
+ const ready = await confirm("Ready to continue?");
34475
+ if (ready === null || !ready) return;
34476
+ await new Promise((r) => setTimeout(r, 3e3));
34477
+ analyzeSpinner.start();
34478
+ activeSpinner = analyzeSpinner;
34479
+ analyzeSpinner.text = "Retrying analysis...";
34480
+ maxFinalizeRetries--;
33728
34481
  } else {
34482
+ analyzeSpinner.stop();
33729
34483
  log.error(finalizeData.message || "Failed to finalize submission");
34484
+ if (!fromMenu) process.exitCode = 1;
34485
+ return;
33730
34486
  }
33731
- process.exit(1);
34487
+ }
34488
+ if (!finalizeSuccess) {
34489
+ analyzeSpinner.stop();
34490
+ log.error("Could not finalize after multiple attempts. Your files are saved -- try again later.");
34491
+ return;
33732
34492
  }
33733
34493
  analyzeSpinner.text = "AI review in progress...";
33734
34494
  const reportData = await pollForReport(submissionId, analyzeSpinner);
33735
- analyzeSpinner.stop();
34495
+ analyzeSpinner.succeed("Analysis complete!");
33736
34496
  if (reportData.status === "complete" && reportData.data) {
33737
34497
  if (options.json) {
33738
34498
  console.log(JSON.stringify(reportData.data, null, 2));
@@ -33740,43 +34500,59 @@ async function submitCommand(path5, options = {}) {
33740
34500
  renderReport(reportData.data.report, reportData.data.items);
33741
34501
  console.log(subtext(` Full report: https://preflightlaunch.com/report/${reportData.data.report.id}`));
33742
34502
  console.log();
33743
- const next = await select({
33744
- message: "What next?",
33745
- options: [
33746
- { value: "open", label: "Open full report in browser" },
33747
- { value: "another", label: "Submit a different app" },
33748
- { value: "done", label: "Done" }
33749
- ]
33750
- });
33751
- if (next === "open") {
33752
- const open = (await import("./open-A77P4RC4.js")).default;
33753
- await open(`https://preflightlaunch.com/report/${reportData.data.report.id}`);
33754
- } else if (next === "another") {
33755
- await submitCommand();
34503
+ if (!fromMenu) {
34504
+ const next = await select({
34505
+ message: "What next?",
34506
+ options: [
34507
+ { value: "open", label: "Open full report in browser" },
34508
+ { value: "done", label: "Done" }
34509
+ ]
34510
+ });
34511
+ if (next === "open") {
34512
+ await openUrl(`https://preflightlaunch.com/report/${reportData.data.report.id}`);
34513
+ }
33756
34514
  }
33757
34515
  }
33758
34516
  } else if (reportData.status === "failed") {
33759
34517
  log.error("Analysis failed. Please try submitting again or contact support.");
33760
- process.exit(1);
34518
+ if (!fromMenu) process.exitCode = 1;
33761
34519
  } else {
33762
34520
  log.warning("Analysis is still running. Check status with:");
33763
34521
  console.log(subtext(` preflight status ${submissionId}`));
33764
34522
  }
33765
34523
  } catch (err) {
33766
- spinner2.stop();
34524
+ activeSpinner.stop();
33767
34525
  log.error(`Submit failed: ${err instanceof Error ? err.message : "Unknown error"}`);
33768
- process.exit(1);
34526
+ if (!fromMenu) process.exitCode = 1;
34527
+ }
34528
+ }
34529
+ function buildSummary(appName, dir, files, compliance) {
34530
+ const home = __require("os").homedir();
34531
+ const shortDir = dir.startsWith(home) ? "~" + dir.slice(home.length) : dir;
34532
+ const fileTypes = files.map((f) => f.filename).join(", ");
34533
+ const screenshotCount = files.filter((f) => f.type === "screenshot").length;
34534
+ let summary = `App: ${appName}
34535
+ `;
34536
+ summary += `Project: ${shortDir}
34537
+ `;
34538
+ summary += `Files: ${fileTypes}${screenshotCount > 0 ? ` (${screenshotCount} screenshots)` : ""}
34539
+ `;
34540
+ if (compliance) {
34541
+ const complianceLines = formatComplianceSummary(compliance);
34542
+ summary += complianceLines.map((l) => l.trim()).join("\n");
33769
34543
  }
34544
+ return summary;
33770
34545
  }
33771
34546
  function getFileSize(filePath) {
33772
34547
  try {
33773
- return statSync2(filePath).size;
34548
+ return statSync3(filePath).size;
33774
34549
  } catch {
33775
34550
  return 0;
33776
34551
  }
33777
34552
  }
33778
34553
  async function pollForReport(submissionId, spinner2, maxAttempts = 60, interval = 5e3) {
33779
34554
  let consecutiveFailures = 0;
34555
+ const startTime = Date.now();
33780
34556
  for (let i = 0; i < maxAttempts; i++) {
33781
34557
  await new Promise((r) => setTimeout(r, interval));
33782
34558
  const res = await apiRequest(`/api/submissions/${submissionId}`);
@@ -33793,9 +34569,9 @@ async function pollForReport(submissionId, spinner2, maxAttempts = 60, interval
33793
34569
  consecutiveFailures = 0;
33794
34570
  const data = await res.json();
33795
34571
  const submission = data.data;
33796
- const stages = ["Files uploaded", "Metadata validated", "AI review in progress...", "Generating report"];
33797
- const stageIdx = Math.min(Math.floor((i + 1) / maxAttempts * stages.length), stages.length - 1);
33798
- spinner2.text = `${stages[stageIdx]} (${Math.min((i + 1) * 3, 95)}%)`;
34572
+ const elapsed = Math.round((Date.now() - startTime) / 1e3);
34573
+ const status = submission.status || "analyzing";
34574
+ spinner2.text = `AI review in progress... (${elapsed}s elapsed)`;
33799
34575
  if (submission.status === "complete") {
33800
34576
  if (submission.report_id) {
33801
34577
  const reportRes = await apiRequest(`/api/reports/${submission.report_id}`);
@@ -33816,7 +34592,9 @@ export {
33816
34592
  createSpinner,
33817
34593
  success,
33818
34594
  error,
34595
+ DEFAULT_API_URL,
33819
34596
  getConfig,
34597
+ clearAuth,
33820
34598
  isLoggedIn,
33821
34599
  hasRunBefore,
33822
34600
  markAsRun,
@@ -33832,19 +34610,16 @@ export {
33832
34610
  critical,
33833
34611
  icons,
33834
34612
  intro,
33835
- showTagline,
33836
- brandSplash,
33837
- outro,
33838
34613
  tip,
34614
+ renderHeader,
33839
34615
  select,
34616
+ confirm,
33840
34617
  spinner,
33841
34618
  log,
33842
- findXcodeProjects,
33843
- findProjectInDir,
33844
34619
  interactiveProjectSelect,
33845
34620
  renderReport,
33846
34621
  renderReportJson,
33847
34622
  handleUnknownCommand,
33848
34623
  submitCommand
33849
34624
  };
33850
- //# sourceMappingURL=chunk-X5CBMYPG.js.map
34625
+ //# sourceMappingURL=chunk-PMKDGQCB.js.map