@expo/cli 56.1.3 → 56.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/build/bin/cli +1 -1
  2. package/build/src/api/endpoint.js +1 -1
  3. package/build/src/api/endpoint.js.map +1 -1
  4. package/build/src/api/user/actions.js +7 -0
  5. package/build/src/api/user/actions.js.map +1 -1
  6. package/build/src/api/user/expoSsoLauncher.js +76 -24
  7. package/build/src/api/user/expoSsoLauncher.js.map +1 -1
  8. package/build/src/api/user/user.js +12 -0
  9. package/build/src/api/user/user.js.map +1 -1
  10. package/build/src/customize/typescript.js.map +1 -1
  11. package/build/src/events/index.js +1 -1
  12. package/build/src/start/platforms/ios/simctl.js +4 -0
  13. package/build/src/start/platforms/ios/simctl.js.map +1 -1
  14. package/build/src/start/server/metro/MetroBundlerDevServer.js +57 -45
  15. package/build/src/start/server/metro/MetroBundlerDevServer.js.map +1 -1
  16. package/build/src/start/server/metro/debugging/createDebugMiddleware.js +1 -1
  17. package/build/src/start/server/metro/debugging/createDebugMiddleware.js.map +1 -1
  18. package/build/src/start/server/metro/instantiateMetro.js +2 -1
  19. package/build/src/start/server/metro/instantiateMetro.js.map +1 -1
  20. package/build/src/start/server/metro/runServer-fork.js +10 -1
  21. package/build/src/start/server/metro/runServer-fork.js.map +1 -1
  22. package/build/src/start/server/metro/withMetroMultiPlatform.js +8 -4
  23. package/build/src/start/server/metro/withMetroMultiPlatform.js.map +1 -1
  24. package/build/src/start/server/serverLogLikeMetro.js +2 -2
  25. package/build/src/start/server/serverLogLikeMetro.js.map +1 -1
  26. package/build/src/start/server/type-generation/routes.js +1 -3
  27. package/build/src/start/server/type-generation/routes.js.map +1 -1
  28. package/build/src/utils/open.js +243 -11
  29. package/build/src/utils/open.js.map +1 -1
  30. package/build/src/utils/telemetry/clients/FetchClient.js +1 -1
  31. package/build/src/utils/telemetry/utils/context.js +1 -1
  32. package/package.json +17 -18
  33. package/static/loading-page/index.html +35 -1
package/build/bin/cli CHANGED
@@ -139,7 +139,7 @@ const args = (0, _arg().default)({
139
139
  });
140
140
  if (args['--version']) {
141
141
  // Version is added in the build script.
142
- console.log("56.1.3");
142
+ console.log("56.1.5");
143
143
  process.exit(0);
144
144
  }
145
145
  if (args['--non-interactive']) {
@@ -30,7 +30,7 @@ function getExpoWebsiteBaseUrl() {
30
30
  if (_env.env.EXPO_STAGING) {
31
31
  return `https://staging.expo.dev`;
32
32
  } else if (_env.env.EXPO_LOCAL) {
33
- return `http://127.0.0.1:3001`;
33
+ return `http://expo.test`;
34
34
  } else {
35
35
  return `https://expo.dev`;
36
36
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/api/endpoint.ts"],"sourcesContent":["import { env } from '../utils/env';\n\n/** Get the URL for the expo.dev API. */\nexport function getExpoApiBaseUrl(): string {\n if (env.EXPO_STAGING) {\n return `https://staging-api.expo.dev`;\n } else if (env.EXPO_LOCAL) {\n return `http://127.0.0.1:3000`;\n } else {\n return `https://api.expo.dev`;\n }\n}\n\n/** Get the URL for the expo.dev website. */\nexport function getExpoWebsiteBaseUrl(): string {\n if (env.EXPO_STAGING) {\n return `https://staging.expo.dev`;\n } else if (env.EXPO_LOCAL) {\n return `http://127.0.0.1:3001`;\n } else {\n return `https://expo.dev`;\n }\n}\n"],"names":["getExpoApiBaseUrl","getExpoWebsiteBaseUrl","env","EXPO_STAGING","EXPO_LOCAL"],"mappings":";;;;;;;;;;;QAGgBA;eAAAA;;QAWAC;eAAAA;;;qBAdI;AAGb,SAASD;IACd,IAAIE,QAAG,CAACC,YAAY,EAAE;QACpB,OAAO,CAAC,4BAA4B,CAAC;IACvC,OAAO,IAAID,QAAG,CAACE,UAAU,EAAE;QACzB,OAAO,CAAC,qBAAqB,CAAC;IAChC,OAAO;QACL,OAAO,CAAC,oBAAoB,CAAC;IAC/B;AACF;AAGO,SAASH;IACd,IAAIC,QAAG,CAACC,YAAY,EAAE;QACpB,OAAO,CAAC,wBAAwB,CAAC;IACnC,OAAO,IAAID,QAAG,CAACE,UAAU,EAAE;QACzB,OAAO,CAAC,qBAAqB,CAAC;IAChC,OAAO;QACL,OAAO,CAAC,gBAAgB,CAAC;IAC3B;AACF"}
1
+ {"version":3,"sources":["../../../src/api/endpoint.ts"],"sourcesContent":["import { env } from '../utils/env';\n\n/** Get the URL for the expo.dev API. */\nexport function getExpoApiBaseUrl(): string {\n if (env.EXPO_STAGING) {\n return `https://staging-api.expo.dev`;\n } else if (env.EXPO_LOCAL) {\n return `http://127.0.0.1:3000`;\n } else {\n return `https://api.expo.dev`;\n }\n}\n\n/** Get the URL for the expo.dev website. */\nexport function getExpoWebsiteBaseUrl(): string {\n if (env.EXPO_STAGING) {\n return `https://staging.expo.dev`;\n } else if (env.EXPO_LOCAL) {\n return `http://expo.test`;\n } else {\n return `https://expo.dev`;\n }\n}\n"],"names":["getExpoApiBaseUrl","getExpoWebsiteBaseUrl","env","EXPO_STAGING","EXPO_LOCAL"],"mappings":";;;;;;;;;;;QAGgBA;eAAAA;;QAWAC;eAAAA;;;qBAdI;AAGb,SAASD;IACd,IAAIE,QAAG,CAACC,YAAY,EAAE;QACpB,OAAO,CAAC,4BAA4B,CAAC;IACvC,OAAO,IAAID,QAAG,CAACE,UAAU,EAAE;QACzB,OAAO,CAAC,qBAAqB,CAAC;IAChC,OAAO;QACL,OAAO,CAAC,oBAAoB,CAAC;IAC/B;AACF;AAGO,SAASH;IACd,IAAIC,QAAG,CAACC,YAAY,EAAE;QACpB,OAAO,CAAC,wBAAwB,CAAC;IACnC,OAAO,IAAID,QAAG,CAACE,UAAU,EAAE;QACzB,OAAO,CAAC,gBAAgB,CAAC;IAC3B,OAAO;QACL,OAAO,CAAC,gBAAgB,CAAC;IAC3B;AACF"}
@@ -35,6 +35,7 @@ const _user = require("./user");
35
35
  const _log = /*#__PURE__*/ _interop_require_wildcard(require("../../log"));
36
36
  const _env = require("../../utils/env");
37
37
  const _errors = require("../../utils/errors");
38
+ const _interactive = require("../../utils/interactive");
38
39
  const _link = require("../../utils/link");
39
40
  const _prompts = /*#__PURE__*/ _interop_require_wildcard(require("../../utils/prompts"));
40
41
  const _client = require("../rest/client");
@@ -142,6 +143,12 @@ async function tryGetUserAsync() {
142
143
  if (user) {
143
144
  return user;
144
145
  }
146
+ // In non-interactive environments (CI, non-TTY) we can't prompt for login. Proceed
147
+ // anonymously so callers like the Expo Go manifest code-signing flow degrade
148
+ // gracefully instead of bubbling a NON_INTERACTIVE error to the client.
149
+ if (!(0, _interactive.isInteractive)()) {
150
+ return null;
151
+ }
145
152
  const choices = [
146
153
  {
147
154
  title: 'Log in',
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/api/user/actions.ts"],"sourcesContent":["import assert from 'assert';\nimport chalk from 'chalk';\n\nimport { retryUsernamePasswordAuthWithOTPAsync } from './otp';\nimport type { Actor } from './user';\nimport { getUserAsync, loginAsync, browserLoginAsync } from './user';\nimport * as Log from '../../log';\nimport { env } from '../../utils/env';\nimport { CommandError } from '../../utils/errors';\nimport { learnMore } from '../../utils/link';\nimport type { Question } from '../../utils/prompts';\nimport promptAsync, { selectAsync } from '../../utils/prompts';\nimport { ApiV2Error } from '../rest/client';\n\n/** Show login prompt while prompting for missing credentials. */\nexport async function showLoginPromptAsync({\n printNewLine = false,\n otp,\n ...options\n}: {\n printNewLine?: boolean;\n username?: string;\n password?: string;\n otp?: string;\n sso?: boolean | undefined;\n browser?: boolean | undefined;\n} = {}): Promise<void> {\n if (env.EXPO_OFFLINE) {\n throw new CommandError('OFFLINE', 'Cannot authenticate in offline-mode');\n }\n const hasCredentials = options.username && options.password;\n const sso = options.sso;\n const browser = options.browser;\n\n if (printNewLine) {\n Log.log();\n }\n\n if (sso || browser) {\n await browserLoginAsync({ sso: !!sso });\n return;\n }\n\n Log.log(\n hasCredentials\n ? `Logging in to EAS with email or username (exit and run 'npx expo login --help' for other login options)`\n : `Log in to EAS with email or username (exit and run 'npx expo login --help' for other login options)`\n );\n\n let username = options.username;\n let password = options.password;\n\n if (!hasCredentials) {\n const resolved = await promptAsync(\n [\n !options.username && {\n type: 'text',\n name: 'username',\n message: 'Email or username',\n },\n !options.password && {\n type: 'password',\n name: 'password',\n message: 'Password',\n },\n ].filter(Boolean) as Question<string>[],\n {\n nonInteractiveHelp: `Use the EXPO_TOKEN environment variable to authenticate in CI (${learnMore(\n 'https://docs.expo.dev/accounts/programmatic-access/'\n )})`,\n }\n );\n username ??= resolved.username;\n password ??= resolved.password;\n }\n // This is just for the types.\n assert(username && password);\n\n try {\n await loginAsync({\n username,\n password,\n otp,\n });\n } catch (e) {\n if (e instanceof ApiV2Error && e.expoApiV2ErrorCode === 'ONE_TIME_PASSWORD_REQUIRED') {\n await retryUsernamePasswordAuthWithOTPAsync(\n username,\n password,\n e.expoApiV2ErrorMetadata as any\n );\n } else {\n throw e;\n }\n }\n}\n\nexport async function tryGetUserAsync(): Promise<Actor | null> {\n const user = await getUserAsync().catch(() => null);\n\n if (user) {\n return user;\n }\n\n const choices = [\n {\n title: 'Log in',\n value: true,\n },\n {\n title: 'Proceed anonymously',\n value: false,\n },\n ];\n\n const value = await selectAsync(\n chalk`\\n\\nIt is recommended to log in with your Expo account before proceeding. \\n{dim ${learnMore(\n 'https://expo.fyi/unverified-app-expo-go'\n )}}\\n`,\n choices,\n {\n nonInteractiveHelp: `Use the EXPO_TOKEN environment variable to authenticate in CI (${learnMore(\n 'https://docs.expo.dev/accounts/programmatic-access/'\n )})`,\n }\n );\n\n if (value) {\n await showLoginPromptAsync({ printNewLine: true });\n return (await getUserAsync()) ?? null;\n }\n\n return null;\n}\n"],"names":["showLoginPromptAsync","tryGetUserAsync","printNewLine","otp","options","env","EXPO_OFFLINE","CommandError","hasCredentials","username","password","sso","browser","Log","log","browserLoginAsync","resolved","promptAsync","type","name","message","filter","Boolean","nonInteractiveHelp","learnMore","assert","loginAsync","e","ApiV2Error","expoApiV2ErrorCode","retryUsernamePasswordAuthWithOTPAsync","expoApiV2ErrorMetadata","user","getUserAsync","catch","choices","title","value","selectAsync","chalk"],"mappings":";;;;;;;;;;;QAesBA;eAAAA;;QAkFAC;eAAAA;;;;gEAjGH;;;;;;;gEACD;;;;;;qBAEoC;sBAEM;6DACvC;qBACD;wBACS;sBACH;iEAEe;wBACd;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAGpB,eAAeD,qBAAqB,EACzCE,eAAe,KAAK,EACpBC,GAAG,EACH,GAAGC,SAQJ,GAAG,CAAC,CAAC;IACJ,IAAIC,QAAG,CAACC,YAAY,EAAE;QACpB,MAAM,IAAIC,oBAAY,CAAC,WAAW;IACpC;IACA,MAAMC,iBAAiBJ,QAAQK,QAAQ,IAAIL,QAAQM,QAAQ;IAC3D,MAAMC,MAAMP,QAAQO,GAAG;IACvB,MAAMC,UAAUR,QAAQQ,OAAO;IAE/B,IAAIV,cAAc;QAChBW,KAAIC,GAAG;IACT;IAEA,IAAIH,OAAOC,SAAS;QAClB,MAAMG,IAAAA,uBAAiB,EAAC;YAAEJ,KAAK,CAAC,CAACA;QAAI;QACrC;IACF;IAEAE,KAAIC,GAAG,CACLN,iBACI,CAAC,uGAAuG,CAAC,GACzG,CAAC,mGAAmG,CAAC;IAG3G,IAAIC,WAAWL,QAAQK,QAAQ;IAC/B,IAAIC,WAAWN,QAAQM,QAAQ;IAE/B,IAAI,CAACF,gBAAgB;QACnB,MAAMQ,WAAW,MAAMC,IAAAA,gBAAW,EAChC;YACE,CAACb,QAAQK,QAAQ,IAAI;gBACnBS,MAAM;gBACNC,MAAM;gBACNC,SAAS;YACX;YACA,CAAChB,QAAQM,QAAQ,IAAI;gBACnBQ,MAAM;gBACNC,MAAM;gBACNC,SAAS;YACX;SACD,CAACC,MAAM,CAACC,UACT;YACEC,oBAAoB,CAAC,+DAA+D,EAAEC,IAAAA,eAAS,EAC7F,uDACA,CAAC,CAAC;QACN;QAEFf,aAAaO,SAASP,QAAQ;QAC9BC,aAAaM,SAASN,QAAQ;IAChC;IACA,8BAA8B;IAC9Be,IAAAA,iBAAM,EAAChB,YAAYC;IAEnB,IAAI;QACF,MAAMgB,IAAAA,gBAAU,EAAC;YACfjB;YACAC;YACAP;QACF;IACF,EAAE,OAAOwB,GAAG;QACV,IAAIA,aAAaC,kBAAU,IAAID,EAAEE,kBAAkB,KAAK,8BAA8B;YACpF,MAAMC,IAAAA,0CAAqC,EACzCrB,UACAC,UACAiB,EAAEI,sBAAsB;QAE5B,OAAO;YACL,MAAMJ;QACR;IACF;AACF;AAEO,eAAe1B;IACpB,MAAM+B,OAAO,MAAMC,IAAAA,kBAAY,IAAGC,KAAK,CAAC,IAAM;IAE9C,IAAIF,MAAM;QACR,OAAOA;IACT;IAEA,MAAMG,UAAU;QACd;YACEC,OAAO;YACPC,OAAO;QACT;QACA;YACED,OAAO;YACPC,OAAO;QACT;KACD;IAED,MAAMA,QAAQ,MAAMC,IAAAA,oBAAW,EAC7BC,IAAAA,gBAAK,CAAA,CAAC,iFAAiF,EAAEf,IAAAA,eAAS,EAChG,2CACA,GAAG,CAAC,EACNW,SACA;QACEZ,oBAAoB,CAAC,+DAA+D,EAAEC,IAAAA,eAAS,EAC7F,uDACA,CAAC,CAAC;IACN;IAGF,IAAIa,OAAO;QACT,MAAMrC,qBAAqB;YAAEE,cAAc;QAAK;QAChD,OAAO,AAAC,MAAM+B,IAAAA,kBAAY,OAAO;IACnC;IAEA,OAAO;AACT"}
1
+ {"version":3,"sources":["../../../../src/api/user/actions.ts"],"sourcesContent":["import assert from 'assert';\nimport chalk from 'chalk';\n\nimport { retryUsernamePasswordAuthWithOTPAsync } from './otp';\nimport type { Actor } from './user';\nimport { getUserAsync, loginAsync, browserLoginAsync } from './user';\nimport * as Log from '../../log';\nimport { env } from '../../utils/env';\nimport { CommandError } from '../../utils/errors';\nimport { isInteractive } from '../../utils/interactive';\nimport { learnMore } from '../../utils/link';\nimport type { Question } from '../../utils/prompts';\nimport promptAsync, { selectAsync } from '../../utils/prompts';\nimport { ApiV2Error } from '../rest/client';\n\n/** Show login prompt while prompting for missing credentials. */\nexport async function showLoginPromptAsync({\n printNewLine = false,\n otp,\n ...options\n}: {\n printNewLine?: boolean;\n username?: string;\n password?: string;\n otp?: string;\n sso?: boolean | undefined;\n browser?: boolean | undefined;\n} = {}): Promise<void> {\n if (env.EXPO_OFFLINE) {\n throw new CommandError('OFFLINE', 'Cannot authenticate in offline-mode');\n }\n const hasCredentials = options.username && options.password;\n const sso = options.sso;\n const browser = options.browser;\n\n if (printNewLine) {\n Log.log();\n }\n\n if (sso || browser) {\n await browserLoginAsync({ sso: !!sso });\n return;\n }\n\n Log.log(\n hasCredentials\n ? `Logging in to EAS with email or username (exit and run 'npx expo login --help' for other login options)`\n : `Log in to EAS with email or username (exit and run 'npx expo login --help' for other login options)`\n );\n\n let username = options.username;\n let password = options.password;\n\n if (!hasCredentials) {\n const resolved = await promptAsync(\n [\n !options.username && {\n type: 'text',\n name: 'username',\n message: 'Email or username',\n },\n !options.password && {\n type: 'password',\n name: 'password',\n message: 'Password',\n },\n ].filter(Boolean) as Question<string>[],\n {\n nonInteractiveHelp: `Use the EXPO_TOKEN environment variable to authenticate in CI (${learnMore(\n 'https://docs.expo.dev/accounts/programmatic-access/'\n )})`,\n }\n );\n username ??= resolved.username;\n password ??= resolved.password;\n }\n // This is just for the types.\n assert(username && password);\n\n try {\n await loginAsync({\n username,\n password,\n otp,\n });\n } catch (e) {\n if (e instanceof ApiV2Error && e.expoApiV2ErrorCode === 'ONE_TIME_PASSWORD_REQUIRED') {\n await retryUsernamePasswordAuthWithOTPAsync(\n username,\n password,\n e.expoApiV2ErrorMetadata as any\n );\n } else {\n throw e;\n }\n }\n}\n\nexport async function tryGetUserAsync(): Promise<Actor | null> {\n const user = await getUserAsync().catch(() => null);\n\n if (user) {\n return user;\n }\n\n // In non-interactive environments (CI, non-TTY) we can't prompt for login. Proceed\n // anonymously so callers like the Expo Go manifest code-signing flow degrade\n // gracefully instead of bubbling a NON_INTERACTIVE error to the client.\n if (!isInteractive()) {\n return null;\n }\n\n const choices = [\n {\n title: 'Log in',\n value: true,\n },\n {\n title: 'Proceed anonymously',\n value: false,\n },\n ];\n\n const value = await selectAsync(\n chalk`\\n\\nIt is recommended to log in with your Expo account before proceeding. \\n{dim ${learnMore(\n 'https://expo.fyi/unverified-app-expo-go'\n )}}\\n`,\n choices,\n {\n nonInteractiveHelp: `Use the EXPO_TOKEN environment variable to authenticate in CI (${learnMore(\n 'https://docs.expo.dev/accounts/programmatic-access/'\n )})`,\n }\n );\n\n if (value) {\n await showLoginPromptAsync({ printNewLine: true });\n return (await getUserAsync()) ?? null;\n }\n\n return null;\n}\n"],"names":["showLoginPromptAsync","tryGetUserAsync","printNewLine","otp","options","env","EXPO_OFFLINE","CommandError","hasCredentials","username","password","sso","browser","Log","log","browserLoginAsync","resolved","promptAsync","type","name","message","filter","Boolean","nonInteractiveHelp","learnMore","assert","loginAsync","e","ApiV2Error","expoApiV2ErrorCode","retryUsernamePasswordAuthWithOTPAsync","expoApiV2ErrorMetadata","user","getUserAsync","catch","isInteractive","choices","title","value","selectAsync","chalk"],"mappings":";;;;;;;;;;;QAgBsBA;eAAAA;;QAkFAC;eAAAA;;;;gEAlGH;;;;;;;gEACD;;;;;;qBAEoC;sBAEM;6DACvC;qBACD;wBACS;6BACC;sBACJ;iEAEe;wBACd;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAGpB,eAAeD,qBAAqB,EACzCE,eAAe,KAAK,EACpBC,GAAG,EACH,GAAGC,SAQJ,GAAG,CAAC,CAAC;IACJ,IAAIC,QAAG,CAACC,YAAY,EAAE;QACpB,MAAM,IAAIC,oBAAY,CAAC,WAAW;IACpC;IACA,MAAMC,iBAAiBJ,QAAQK,QAAQ,IAAIL,QAAQM,QAAQ;IAC3D,MAAMC,MAAMP,QAAQO,GAAG;IACvB,MAAMC,UAAUR,QAAQQ,OAAO;IAE/B,IAAIV,cAAc;QAChBW,KAAIC,GAAG;IACT;IAEA,IAAIH,OAAOC,SAAS;QAClB,MAAMG,IAAAA,uBAAiB,EAAC;YAAEJ,KAAK,CAAC,CAACA;QAAI;QACrC;IACF;IAEAE,KAAIC,GAAG,CACLN,iBACI,CAAC,uGAAuG,CAAC,GACzG,CAAC,mGAAmG,CAAC;IAG3G,IAAIC,WAAWL,QAAQK,QAAQ;IAC/B,IAAIC,WAAWN,QAAQM,QAAQ;IAE/B,IAAI,CAACF,gBAAgB;QACnB,MAAMQ,WAAW,MAAMC,IAAAA,gBAAW,EAChC;YACE,CAACb,QAAQK,QAAQ,IAAI;gBACnBS,MAAM;gBACNC,MAAM;gBACNC,SAAS;YACX;YACA,CAAChB,QAAQM,QAAQ,IAAI;gBACnBQ,MAAM;gBACNC,MAAM;gBACNC,SAAS;YACX;SACD,CAACC,MAAM,CAACC,UACT;YACEC,oBAAoB,CAAC,+DAA+D,EAAEC,IAAAA,eAAS,EAC7F,uDACA,CAAC,CAAC;QACN;QAEFf,aAAaO,SAASP,QAAQ;QAC9BC,aAAaM,SAASN,QAAQ;IAChC;IACA,8BAA8B;IAC9Be,IAAAA,iBAAM,EAAChB,YAAYC;IAEnB,IAAI;QACF,MAAMgB,IAAAA,gBAAU,EAAC;YACfjB;YACAC;YACAP;QACF;IACF,EAAE,OAAOwB,GAAG;QACV,IAAIA,aAAaC,kBAAU,IAAID,EAAEE,kBAAkB,KAAK,8BAA8B;YACpF,MAAMC,IAAAA,0CAAqC,EACzCrB,UACAC,UACAiB,EAAEI,sBAAsB;QAE5B,OAAO;YACL,MAAMJ;QACR;IACF;AACF;AAEO,eAAe1B;IACpB,MAAM+B,OAAO,MAAMC,IAAAA,kBAAY,IAAGC,KAAK,CAAC,IAAM;IAE9C,IAAIF,MAAM;QACR,OAAOA;IACT;IAEA,mFAAmF;IACnF,6EAA6E;IAC7E,wEAAwE;IACxE,IAAI,CAACG,IAAAA,0BAAa,KAAI;QACpB,OAAO;IACT;IAEA,MAAMC,UAAU;QACd;YACEC,OAAO;YACPC,OAAO;QACT;QACA;YACED,OAAO;YACPC,OAAO;QACT;KACD;IAED,MAAMA,QAAQ,MAAMC,IAAAA,oBAAW,EAC7BC,IAAAA,gBAAK,CAAA,CAAC,iFAAiF,EAAEhB,IAAAA,eAAS,EAChG,2CACA,GAAG,CAAC,EACNY,SACA;QACEb,oBAAoB,CAAC,+DAA+D,EAAEC,IAAAA,eAAS,EAC7F,uDACA,CAAC,CAAC;IACN;IAGF,IAAIc,OAAO;QACT,MAAMtC,qBAAqB;YAAEE,cAAc;QAAK;QAChD,OAAO,AAAC,MAAM+B,IAAAA,kBAAY,OAAO;IACnC;IAEA,OAAO;AACT"}
@@ -15,9 +15,9 @@ function _assert() {
15
15
  };
16
16
  return data;
17
17
  }
18
- function _betteropn() {
19
- const data = /*#__PURE__*/ _interop_require_default(require("better-opn"));
20
- _betteropn = function() {
18
+ function _crypto() {
19
+ const data = /*#__PURE__*/ _interop_require_default(require("crypto"));
20
+ _crypto = function() {
21
21
  return data;
22
22
  };
23
23
  return data;
@@ -29,14 +29,10 @@ function _http() {
29
29
  };
30
30
  return data;
31
31
  }
32
- function _querystring() {
33
- const data = /*#__PURE__*/ _interop_require_default(require("querystring"));
34
- _querystring = function() {
35
- return data;
36
- };
37
- return data;
38
- }
39
32
  const _log = /*#__PURE__*/ _interop_require_wildcard(require("../../log"));
33
+ const _errors = require("../../utils/errors");
34
+ const _open = require("../../utils/open");
35
+ const _client = require("../rest/client");
40
36
  function _interop_require_default(obj) {
41
37
  return obj && obj.__esModule ? obj : {
42
38
  default: obj
@@ -83,15 +79,57 @@ function _interop_require_wildcard(obj, nodeInterop) {
83
79
  }
84
80
  return newObj;
85
81
  }
82
+ const CLIENT_ID = 'expo-cli';
83
+ function generateCodeVerifier() {
84
+ return _crypto().default.randomBytes(32).toString('base64url');
85
+ }
86
+ function generateCodeChallenge(codeVerifier) {
87
+ return _crypto().default.createHash('sha256').update(codeVerifier).digest('base64url');
88
+ }
89
+ function generateState() {
90
+ return _crypto().default.randomBytes(32).toString('base64url');
91
+ }
92
+ async function exchangeCodeForSessionSecretAsync({ code, codeVerifier, redirectUri }) {
93
+ const response = await (0, _client.fetchAsync)('auth/token', {
94
+ method: 'POST',
95
+ headers: {
96
+ 'Content-Type': 'application/json'
97
+ },
98
+ body: JSON.stringify({
99
+ grant_type: 'authorization_code',
100
+ code,
101
+ redirect_uri: redirectUri,
102
+ code_verifier: codeVerifier,
103
+ client_id: CLIENT_ID
104
+ })
105
+ });
106
+ const { session_secret: sessionSecret } = (0, _client.getResponseDataOrThrow)(await response.json());
107
+ if (!sessionSecret) {
108
+ throw new _errors.CommandError('BROWSER_AUTH', 'Failed to obtain session secret from token exchange.');
109
+ }
110
+ return sessionSecret;
111
+ }
86
112
  async function getSessionUsingBrowserAuthFlowAsync({ expoWebsiteUrl, sso = false }) {
87
113
  const scheme = 'http';
88
114
  const hostname = 'localhost';
89
- const path = '/auth/callback';
115
+ const callbackPath = '/auth/callback';
116
+ const codeVerifier = generateCodeVerifier();
117
+ const codeChallenge = generateCodeChallenge(codeVerifier);
118
+ const state = generateState();
119
+ const buildRedirectUri = (port)=>`${scheme}://${hostname}:${port}${callbackPath}`;
90
120
  const buildExpoLoginUrl = (port, sso)=>{
91
- const params = _querystring().default.stringify({
92
- confirm_account: 'true',
93
- app_redirect_uri: `${scheme}://${hostname}:${port}${path}`
94
- });
121
+ // Note: we avoid URLSearchParams here because better-opn calls encodeURI()
122
+ // on the URL before passing it to AppleScript, which would double-encode
123
+ // the percent-encoded values from URLSearchParams.toString().
124
+ const params = [
125
+ `client_id=${CLIENT_ID}`,
126
+ `redirect_uri=${buildRedirectUri(port)}`,
127
+ `response_type=code`,
128
+ `code_challenge=${codeChallenge}`,
129
+ `code_challenge_method=S256`,
130
+ `state=${state}`,
131
+ `confirm_account=true`
132
+ ].join('&');
95
133
  return `${expoWebsiteUrl}${sso ? '/sso-login' : '/login'}?${params}`;
96
134
  };
97
135
  // Start server and begin auth flow
@@ -110,22 +148,35 @@ async function getSessionUsingBrowserAuthFlowAsync({ expoWebsiteUrl, sso = false
110
148
  connection.destroy();
111
149
  }
112
150
  };
113
- try {
151
+ const handleRequestAsync = async ()=>{
114
152
  var _request_url;
115
- if (!(request.method === 'GET' && ((_request_url = request.url) == null ? void 0 : _request_url.includes('/auth/callback')))) {
116
- throw new Error('Unexpected login response.');
153
+ if (!(request.method === 'GET' && ((_request_url = request.url) == null ? void 0 : _request_url.includes(callbackPath)))) {
154
+ throw new _errors.CommandError('BROWSER_AUTH', 'Unexpected login response.');
117
155
  }
118
156
  const url = new URL(request.url, `http:${request.headers.host}`);
119
- const sessionSecret = url.searchParams.get('session_secret');
120
- if (!sessionSecret) {
121
- throw new Error('Request missing session_secret search parameter.');
157
+ const code = url.searchParams.get('code');
158
+ const returnedState = url.searchParams.get('state');
159
+ if (!code) {
160
+ throw new _errors.CommandError('BROWSER_AUTH', 'Request missing code search parameter.');
161
+ }
162
+ if (returnedState !== state) {
163
+ throw new _errors.CommandError('BROWSER_AUTH', 'State mismatch. Possible CSRF attack.');
122
164
  }
165
+ const address = server.address();
166
+ (0, _assert().default)(address !== null && typeof address === 'object');
167
+ const redirectUri = buildRedirectUri(address.port);
168
+ const sessionSecret = await exchangeCodeForSessionSecretAsync({
169
+ code,
170
+ codeVerifier,
171
+ redirectUri
172
+ });
123
173
  resolve(sessionSecret);
124
174
  redirectAndCleanup('success');
125
- } catch (error) {
175
+ };
176
+ handleRequestAsync().catch((error)=>{
126
177
  redirectAndCleanup('error');
127
178
  reject(error);
128
- }
179
+ });
129
180
  });
130
181
  server.listen(0, hostname, ()=>{
131
182
  _log.log('Waiting for browser login...');
@@ -133,7 +184,8 @@ async function getSessionUsingBrowserAuthFlowAsync({ expoWebsiteUrl, sso = false
133
184
  (0, _assert().default)(address !== null && typeof address === 'object', 'Server address and port should be set after listening has begun');
134
185
  const port = address.port;
135
186
  const authorizeUrl = buildExpoLoginUrl(port, sso);
136
- (0, _betteropn().default)(authorizeUrl);
187
+ _log.log(`If your browser doesn't automatically open, visit this link to log in: ${authorizeUrl}`);
188
+ (0, _open.openBrowserAsync)(authorizeUrl);
137
189
  });
138
190
  server.on('connection', (connection)=>{
139
191
  connections.add(connection);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/api/user/expoSsoLauncher.ts"],"sourcesContent":["import assert from 'assert';\nimport openBrowserAsync from 'better-opn';\nimport http from 'http';\nimport type { Socket } from 'node:net';\nimport querystring from 'querystring';\n\nimport * as Log from '../../log';\n\nexport async function getSessionUsingBrowserAuthFlowAsync({\n expoWebsiteUrl,\n sso = false,\n}: {\n expoWebsiteUrl: string;\n sso?: boolean;\n}): Promise<string> {\n const scheme = 'http';\n const hostname = 'localhost';\n const path = '/auth/callback';\n\n const buildExpoLoginUrl = (port: number, sso: boolean): string => {\n const params = querystring.stringify({\n confirm_account: 'true',\n app_redirect_uri: `${scheme}://${hostname}:${port}${path}`,\n });\n return `${expoWebsiteUrl}${sso ? '/sso-login' : '/login'}?${params}`;\n };\n\n // Start server and begin auth flow\n const executeAuthFlow = (): Promise<string> => {\n return new Promise<string>(async (resolve, reject) => {\n const connections = new Set<Socket>();\n\n const server = http.createServer(\n (request: http.IncomingMessage, response: http.ServerResponse) => {\n const redirectAndCleanup = (result: 'success' | 'error'): void => {\n const redirectUrl = `${expoWebsiteUrl}/oauth/expo-cli?result=${result}`;\n response.writeHead(302, { Location: redirectUrl });\n response.end();\n server.close();\n for (const connection of connections) {\n connection.destroy();\n }\n };\n\n try {\n if (!(request.method === 'GET' && request.url?.includes('/auth/callback'))) {\n throw new Error('Unexpected login response.');\n }\n const url = new URL(request.url, `http:${request.headers.host}`);\n const sessionSecret = url.searchParams.get('session_secret');\n\n if (!sessionSecret) {\n throw new Error('Request missing session_secret search parameter.');\n }\n resolve(sessionSecret);\n redirectAndCleanup('success');\n } catch (error) {\n redirectAndCleanup('error');\n reject(error);\n }\n }\n );\n\n server.listen(0, hostname, () => {\n Log.log('Waiting for browser login...');\n\n const address = server.address();\n assert(\n address !== null && typeof address === 'object',\n 'Server address and port should be set after listening has begun'\n );\n const port = address.port;\n const authorizeUrl = buildExpoLoginUrl(port, sso);\n openBrowserAsync(authorizeUrl);\n });\n\n server.on('connection', (connection) => {\n connections.add(connection);\n\n connection.on('close', () => {\n connections.delete(connection);\n });\n });\n });\n };\n\n return await executeAuthFlow();\n}\n"],"names":["getSessionUsingBrowserAuthFlowAsync","expoWebsiteUrl","sso","scheme","hostname","path","buildExpoLoginUrl","port","params","querystring","stringify","confirm_account","app_redirect_uri","executeAuthFlow","Promise","resolve","reject","connections","Set","server","http","createServer","request","response","redirectAndCleanup","result","redirectUrl","writeHead","Location","end","close","connection","destroy","method","url","includes","Error","URL","headers","host","sessionSecret","searchParams","get","error","listen","Log","log","address","assert","authorizeUrl","openBrowserAsync","on","add","delete"],"mappings":";;;;+BAQsBA;;;eAAAA;;;;gEARH;;;;;;;gEACU;;;;;;;gEACZ;;;;;;;gEAEO;;;;;;6DAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEd,eAAeA,oCAAoC,EACxDC,cAAc,EACdC,MAAM,KAAK,EAIZ;IACC,MAAMC,SAAS;IACf,MAAMC,WAAW;IACjB,MAAMC,OAAO;IAEb,MAAMC,oBAAoB,CAACC,MAAcL;QACvC,MAAMM,SAASC,sBAAW,CAACC,SAAS,CAAC;YACnCC,iBAAiB;YACjBC,kBAAkB,GAAGT,OAAO,GAAG,EAAEC,SAAS,CAAC,EAAEG,OAAOF,MAAM;QAC5D;QACA,OAAO,GAAGJ,iBAAiBC,MAAM,eAAe,SAAS,CAAC,EAAEM,QAAQ;IACtE;IAEA,mCAAmC;IACnC,MAAMK,kBAAkB;QACtB,OAAO,IAAIC,QAAgB,OAAOC,SAASC;YACzC,MAAMC,cAAc,IAAIC;YAExB,MAAMC,SAASC,eAAI,CAACC,YAAY,CAC9B,CAACC,SAA+BC;gBAC9B,MAAMC,qBAAqB,CAACC;oBAC1B,MAAMC,cAAc,GAAGzB,eAAe,uBAAuB,EAAEwB,QAAQ;oBACvEF,SAASI,SAAS,CAAC,KAAK;wBAAEC,UAAUF;oBAAY;oBAChDH,SAASM,GAAG;oBACZV,OAAOW,KAAK;oBACZ,KAAK,MAAMC,cAAcd,YAAa;wBACpCc,WAAWC,OAAO;oBACpB;gBACF;gBAEA,IAAI;wBACgCV;oBAAlC,IAAI,CAAEA,CAAAA,QAAQW,MAAM,KAAK,WAASX,eAAAA,QAAQY,GAAG,qBAAXZ,aAAaa,QAAQ,CAAC,kBAAgB,GAAI;wBAC1E,MAAM,IAAIC,MAAM;oBAClB;oBACA,MAAMF,MAAM,IAAIG,IAAIf,QAAQY,GAAG,EAAE,CAAC,KAAK,EAAEZ,QAAQgB,OAAO,CAACC,IAAI,EAAE;oBAC/D,MAAMC,gBAAgBN,IAAIO,YAAY,CAACC,GAAG,CAAC;oBAE3C,IAAI,CAACF,eAAe;wBAClB,MAAM,IAAIJ,MAAM;oBAClB;oBACArB,QAAQyB;oBACRhB,mBAAmB;gBACrB,EAAE,OAAOmB,OAAO;oBACdnB,mBAAmB;oBACnBR,OAAO2B;gBACT;YACF;YAGFxB,OAAOyB,MAAM,CAAC,GAAGxC,UAAU;gBACzByC,KAAIC,GAAG,CAAC;gBAER,MAAMC,UAAU5B,OAAO4B,OAAO;gBAC9BC,IAAAA,iBAAM,EACJD,YAAY,QAAQ,OAAOA,YAAY,UACvC;gBAEF,MAAMxC,OAAOwC,QAAQxC,IAAI;gBACzB,MAAM0C,eAAe3C,kBAAkBC,MAAML;gBAC7CgD,IAAAA,oBAAgB,EAACD;YACnB;YAEA9B,OAAOgC,EAAE,CAAC,cAAc,CAACpB;gBACvBd,YAAYmC,GAAG,CAACrB;gBAEhBA,WAAWoB,EAAE,CAAC,SAAS;oBACrBlC,YAAYoC,MAAM,CAACtB;gBACrB;YACF;QACF;IACF;IAEA,OAAO,MAAMlB;AACf"}
1
+ {"version":3,"sources":["../../../../src/api/user/expoSsoLauncher.ts"],"sourcesContent":["import assert from 'assert';\nimport crypto from 'crypto';\nimport http from 'http';\nimport type { Socket } from 'node:net';\n\nimport * as Log from '../../log';\nimport { CommandError } from '../../utils/errors';\nimport { openBrowserAsync } from '../../utils/open';\nimport { fetchAsync, getResponseDataOrThrow } from '../rest/client';\n\nconst CLIENT_ID = 'expo-cli';\n\nfunction generateCodeVerifier(): string {\n return crypto.randomBytes(32).toString('base64url');\n}\n\nfunction generateCodeChallenge(codeVerifier: string): string {\n return crypto.createHash('sha256').update(codeVerifier).digest('base64url');\n}\n\nfunction generateState(): string {\n return crypto.randomBytes(32).toString('base64url');\n}\n\nasync function exchangeCodeForSessionSecretAsync({\n code,\n codeVerifier,\n redirectUri,\n}: {\n code: string;\n codeVerifier: string;\n redirectUri: string;\n}): Promise<string> {\n const response = await fetchAsync('auth/token', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n grant_type: 'authorization_code',\n code,\n redirect_uri: redirectUri,\n code_verifier: codeVerifier,\n client_id: CLIENT_ID,\n }),\n });\n const { session_secret: sessionSecret } = getResponseDataOrThrow<{ session_secret?: string }>(\n await response.json()\n );\n if (!sessionSecret) {\n throw new CommandError('BROWSER_AUTH', 'Failed to obtain session secret from token exchange.');\n }\n return sessionSecret;\n}\n\nexport async function getSessionUsingBrowserAuthFlowAsync({\n expoWebsiteUrl,\n sso = false,\n}: {\n expoWebsiteUrl: string;\n sso?: boolean;\n}): Promise<string> {\n const scheme = 'http';\n const hostname = 'localhost';\n const callbackPath = '/auth/callback';\n\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = generateCodeChallenge(codeVerifier);\n const state = generateState();\n\n const buildRedirectUri = (port: number): string =>\n `${scheme}://${hostname}:${port}${callbackPath}`;\n\n const buildExpoLoginUrl = (port: number, sso: boolean): string => {\n // Note: we avoid URLSearchParams here because better-opn calls encodeURI()\n // on the URL before passing it to AppleScript, which would double-encode\n // the percent-encoded values from URLSearchParams.toString().\n const params = [\n `client_id=${CLIENT_ID}`,\n `redirect_uri=${buildRedirectUri(port)}`,\n `response_type=code`,\n `code_challenge=${codeChallenge}`,\n `code_challenge_method=S256`,\n `state=${state}`,\n `confirm_account=true`,\n ].join('&');\n return `${expoWebsiteUrl}${sso ? '/sso-login' : '/login'}?${params}`;\n };\n\n // Start server and begin auth flow\n const executeAuthFlow = (): Promise<string> => {\n return new Promise<string>(async (resolve, reject) => {\n const connections = new Set<Socket>();\n\n const server = http.createServer(\n (request: http.IncomingMessage, response: http.ServerResponse) => {\n const redirectAndCleanup = (result: 'success' | 'error'): void => {\n const redirectUrl = `${expoWebsiteUrl}/oauth/expo-cli?result=${result}`;\n response.writeHead(302, { Location: redirectUrl });\n response.end();\n server.close();\n for (const connection of connections) {\n connection.destroy();\n }\n };\n\n const handleRequestAsync = async (): Promise<void> => {\n if (!(request.method === 'GET' && request.url?.includes(callbackPath))) {\n throw new CommandError('BROWSER_AUTH', 'Unexpected login response.');\n }\n const url = new URL(request.url, `http:${request.headers.host}`);\n const code = url.searchParams.get('code');\n const returnedState = url.searchParams.get('state');\n\n if (!code) {\n throw new CommandError('BROWSER_AUTH', 'Request missing code search parameter.');\n }\n if (returnedState !== state) {\n throw new CommandError('BROWSER_AUTH', 'State mismatch. Possible CSRF attack.');\n }\n\n const address = server.address();\n assert(address !== null && typeof address === 'object');\n const redirectUri = buildRedirectUri(address.port);\n\n const sessionSecret = await exchangeCodeForSessionSecretAsync({\n code,\n codeVerifier,\n redirectUri,\n });\n\n resolve(sessionSecret);\n redirectAndCleanup('success');\n };\n\n handleRequestAsync().catch((error) => {\n redirectAndCleanup('error');\n reject(error);\n });\n }\n );\n\n server.listen(0, hostname, () => {\n Log.log('Waiting for browser login...');\n\n const address = server.address();\n assert(\n address !== null && typeof address === 'object',\n 'Server address and port should be set after listening has begun'\n );\n const port = address.port;\n const authorizeUrl = buildExpoLoginUrl(port, sso);\n Log.log(\n `If your browser doesn't automatically open, visit this link to log in: ${authorizeUrl}`\n );\n openBrowserAsync(authorizeUrl);\n });\n\n server.on('connection', (connection) => {\n connections.add(connection);\n\n connection.on('close', () => {\n connections.delete(connection);\n });\n });\n });\n };\n\n return await executeAuthFlow();\n}\n"],"names":["getSessionUsingBrowserAuthFlowAsync","CLIENT_ID","generateCodeVerifier","crypto","randomBytes","toString","generateCodeChallenge","codeVerifier","createHash","update","digest","generateState","exchangeCodeForSessionSecretAsync","code","redirectUri","response","fetchAsync","method","headers","body","JSON","stringify","grant_type","redirect_uri","code_verifier","client_id","session_secret","sessionSecret","getResponseDataOrThrow","json","CommandError","expoWebsiteUrl","sso","scheme","hostname","callbackPath","codeChallenge","state","buildRedirectUri","port","buildExpoLoginUrl","params","join","executeAuthFlow","Promise","resolve","reject","connections","Set","server","http","createServer","request","redirectAndCleanup","result","redirectUrl","writeHead","Location","end","close","connection","destroy","handleRequestAsync","url","includes","URL","host","searchParams","get","returnedState","address","assert","catch","error","listen","Log","log","authorizeUrl","openBrowserAsync","on","add","delete"],"mappings":"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"}
@@ -84,6 +84,7 @@ function _interop_require_wildcard(obj, nodeInterop) {
84
84
  }
85
85
  return newObj;
86
86
  }
87
+ const debug = require('debug')('expo:api:user');
87
88
  let currentUser;
88
89
  const ANONYMOUS_USERNAME = 'anonymous';
89
90
  function getActorDisplayName(user) {
@@ -140,6 +141,17 @@ async function browserLoginAsync({ sso = false }) {
140
141
  });
141
142
  }
142
143
  async function logoutAsync() {
144
+ var _getSession;
145
+ const sessionSecret = (_getSession = (0, _UserSettings.getSession)()) == null ? void 0 : _getSession.sessionSecret;
146
+ if (sessionSecret) {
147
+ try {
148
+ await (0, _client.fetchAsync)('auth/logout', {
149
+ method: 'POST'
150
+ });
151
+ } catch (error) {
152
+ debug('Failed to invalidate session secret on server:', error);
153
+ }
154
+ }
143
155
  currentUser = undefined;
144
156
  await Promise.all([
145
157
  _fs().promises.rm((0, _codesigning.getDevelopmentCodeSigningDirectory)(), {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/api/user/user.ts"],"sourcesContent":["import { promises as fs } from 'fs';\n\nimport { getAccessToken, getSession, setSessionAsync } from './UserSettings';\nimport { getSessionUsingBrowserAuthFlowAsync } from './expoSsoLauncher';\nimport * as Log from '../../log';\nimport { getDevelopmentCodeSigningDirectory } from '../../utils/codesigning';\nimport { env } from '../../utils/env';\nimport { getExpoWebsiteBaseUrl } from '../endpoint';\nimport { UserQuery, type Actor } from '../graphql/queries/UserQuery';\nimport { fetchAsync } from '../rest/client';\n\nlet currentUser: Actor | undefined;\n\nexport const ANONYMOUS_USERNAME = 'anonymous';\n\n/**\n * Resolve the name of the actor, either normal user or robot user.\n * This should be used whenever the \"current user\" needs to be displayed.\n * The display name CANNOT be used as project owner.\n */\nexport function getActorDisplayName(user?: Actor): string {\n switch (user?.__typename) {\n case 'User':\n return user.username;\n case 'SSOUser':\n return user.username;\n case 'Robot':\n return user.firstName ? `${user.firstName} (robot)` : 'robot';\n default:\n return ANONYMOUS_USERNAME;\n }\n}\n\nexport type { Actor };\n\nexport async function getUserAsync(): Promise<Actor | undefined> {\n const hasCredentials = getAccessToken() || getSession()?.sessionSecret;\n if (!env.EXPO_OFFLINE && !currentUser && hasCredentials) {\n const user = await UserQuery.currentUserAsync();\n currentUser = user ?? undefined;\n }\n return currentUser;\n}\n\nexport async function loginAsync(credentials: {\n username: string;\n password: string;\n otp?: string;\n}): Promise<void> {\n const res = await fetchAsync('auth/loginAsync', {\n method: 'POST',\n body: JSON.stringify(credentials),\n });\n const json: any = await res.json();\n const sessionSecret = json.data.sessionSecret;\n\n const userData = await UserQuery.meUserActorAsync({\n 'expo-session': sessionSecret,\n });\n\n await setSessionAsync({\n sessionSecret,\n userId: userData.id,\n username: userData.username,\n currentConnection: 'Username-Password-Authentication',\n });\n}\n\nexport async function browserLoginAsync({ sso = false }): Promise<void> {\n const sessionSecret = await getSessionUsingBrowserAuthFlowAsync({\n expoWebsiteUrl: getExpoWebsiteBaseUrl(),\n sso,\n });\n const userData = await UserQuery.meUserActorAsync({\n 'expo-session': sessionSecret,\n });\n await setSessionAsync({\n sessionSecret,\n userId: userData.id,\n username: userData.username,\n currentConnection: 'Browser-Flow-Authentication',\n });\n}\n\nexport async function logoutAsync(): Promise<void> {\n currentUser = undefined;\n await Promise.all([\n fs.rm(getDevelopmentCodeSigningDirectory(), { recursive: true, force: true }),\n setSessionAsync(undefined),\n ]);\n Log.log('Logged out');\n}\n"],"names":["ANONYMOUS_USERNAME","browserLoginAsync","getActorDisplayName","getUserAsync","loginAsync","logoutAsync","currentUser","user","__typename","username","firstName","getSession","hasCredentials","getAccessToken","sessionSecret","env","EXPO_OFFLINE","UserQuery","currentUserAsync","undefined","credentials","res","fetchAsync","method","body","JSON","stringify","json","data","userData","meUserActorAsync","setSessionAsync","userId","id","currentConnection","sso","getSessionUsingBrowserAuthFlowAsync","expoWebsiteUrl","getExpoWebsiteBaseUrl","Promise","all","fs","rm","getDevelopmentCodeSigningDirectory","recursive","force","Log","log"],"mappings":";;;;;;;;;;;QAaaA;eAAAA;;QAuDSC;eAAAA;;QAhDNC;eAAAA;;QAeMC;eAAAA;;QASAC;eAAAA;;QAwCAC;eAAAA;;;;yBApFS;;;;;;8BAE6B;iCACR;6DAC/B;6BAC8B;qBAC/B;0BACkB;2BACA;wBACX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAE3B,IAAIC;AAEG,MAAMN,qBAAqB;AAO3B,SAASE,oBAAoBK,IAAY;IAC9C,OAAQA,wBAAAA,KAAMC,UAAU;QACtB,KAAK;YACH,OAAOD,KAAKE,QAAQ;QACtB,KAAK;YACH,OAAOF,KAAKE,QAAQ;QACtB,KAAK;YACH,OAAOF,KAAKG,SAAS,GAAG,GAAGH,KAAKG,SAAS,CAAC,QAAQ,CAAC,GAAG;QACxD;YACE,OAAOV;IACX;AACF;AAIO,eAAeG;QACuBQ;IAA3C,MAAMC,iBAAiBC,IAAAA,4BAAc,SAAMF,cAAAA,IAAAA,wBAAU,wBAAVA,YAAcG,aAAa;IACtE,IAAI,CAACC,QAAG,CAACC,YAAY,IAAI,CAACV,eAAeM,gBAAgB;QACvD,MAAML,OAAO,MAAMU,oBAAS,CAACC,gBAAgB;QAC7CZ,cAAcC,QAAQY;IACxB;IACA,OAAOb;AACT;AAEO,eAAeF,WAAWgB,WAIhC;IACC,MAAMC,MAAM,MAAMC,IAAAA,kBAAU,EAAC,mBAAmB;QAC9CC,QAAQ;QACRC,MAAMC,KAAKC,SAAS,CAACN;IACvB;IACA,MAAMO,OAAY,MAAMN,IAAIM,IAAI;IAChC,MAAMb,gBAAgBa,KAAKC,IAAI,CAACd,aAAa;IAE7C,MAAMe,WAAW,MAAMZ,oBAAS,CAACa,gBAAgB,CAAC;QAChD,gBAAgBhB;IAClB;IAEA,MAAMiB,IAAAA,6BAAe,EAAC;QACpBjB;QACAkB,QAAQH,SAASI,EAAE;QACnBxB,UAAUoB,SAASpB,QAAQ;QAC3ByB,mBAAmB;IACrB;AACF;AAEO,eAAejC,kBAAkB,EAAEkC,MAAM,KAAK,EAAE;IACrD,MAAMrB,gBAAgB,MAAMsB,IAAAA,oDAAmC,EAAC;QAC9DC,gBAAgBC,IAAAA,+BAAqB;QACrCH;IACF;IACA,MAAMN,WAAW,MAAMZ,oBAAS,CAACa,gBAAgB,CAAC;QAChD,gBAAgBhB;IAClB;IACA,MAAMiB,IAAAA,6BAAe,EAAC;QACpBjB;QACAkB,QAAQH,SAASI,EAAE;QACnBxB,UAAUoB,SAASpB,QAAQ;QAC3ByB,mBAAmB;IACrB;AACF;AAEO,eAAe7B;IACpBC,cAAca;IACd,MAAMoB,QAAQC,GAAG,CAAC;QAChBC,cAAE,CAACC,EAAE,CAACC,IAAAA,+CAAkC,KAAI;YAAEC,WAAW;YAAMC,OAAO;QAAK;QAC3Ed,IAAAA,6BAAe,EAACZ;KACjB;IACD2B,KAAIC,GAAG,CAAC;AACV"}
1
+ {"version":3,"sources":["../../../../src/api/user/user.ts"],"sourcesContent":["import { promises as fs } from 'fs';\n\nimport { getAccessToken, getSession, setSessionAsync } from './UserSettings';\nimport { getSessionUsingBrowserAuthFlowAsync } from './expoSsoLauncher';\nimport * as Log from '../../log';\nimport { getDevelopmentCodeSigningDirectory } from '../../utils/codesigning';\nimport { env } from '../../utils/env';\nimport { getExpoWebsiteBaseUrl } from '../endpoint';\nimport { UserQuery, type Actor } from '../graphql/queries/UserQuery';\nimport { fetchAsync } from '../rest/client';\n\nconst debug = require('debug')('expo:api:user') as typeof console.log;\n\nlet currentUser: Actor | undefined;\n\nexport const ANONYMOUS_USERNAME = 'anonymous';\n\n/**\n * Resolve the name of the actor, either normal user or robot user.\n * This should be used whenever the \"current user\" needs to be displayed.\n * The display name CANNOT be used as project owner.\n */\nexport function getActorDisplayName(user?: Actor): string {\n switch (user?.__typename) {\n case 'User':\n return user.username;\n case 'SSOUser':\n return user.username;\n case 'Robot':\n return user.firstName ? `${user.firstName} (robot)` : 'robot';\n default:\n return ANONYMOUS_USERNAME;\n }\n}\n\nexport type { Actor };\n\nexport async function getUserAsync(): Promise<Actor | undefined> {\n const hasCredentials = getAccessToken() || getSession()?.sessionSecret;\n if (!env.EXPO_OFFLINE && !currentUser && hasCredentials) {\n const user = await UserQuery.currentUserAsync();\n currentUser = user ?? undefined;\n }\n return currentUser;\n}\n\nexport async function loginAsync(credentials: {\n username: string;\n password: string;\n otp?: string;\n}): Promise<void> {\n const res = await fetchAsync('auth/loginAsync', {\n method: 'POST',\n body: JSON.stringify(credentials),\n });\n const json: any = await res.json();\n const sessionSecret = json.data.sessionSecret;\n\n const userData = await UserQuery.meUserActorAsync({\n 'expo-session': sessionSecret,\n });\n\n await setSessionAsync({\n sessionSecret,\n userId: userData.id,\n username: userData.username,\n currentConnection: 'Username-Password-Authentication',\n });\n}\n\nexport async function browserLoginAsync({ sso = false }): Promise<void> {\n const sessionSecret = await getSessionUsingBrowserAuthFlowAsync({\n expoWebsiteUrl: getExpoWebsiteBaseUrl(),\n sso,\n });\n const userData = await UserQuery.meUserActorAsync({\n 'expo-session': sessionSecret,\n });\n await setSessionAsync({\n sessionSecret,\n userId: userData.id,\n username: userData.username,\n currentConnection: 'Browser-Flow-Authentication',\n });\n}\n\nexport async function logoutAsync(): Promise<void> {\n const sessionSecret = getSession()?.sessionSecret;\n if (sessionSecret) {\n try {\n await fetchAsync('auth/logout', { method: 'POST' });\n } catch (error) {\n debug('Failed to invalidate session secret on server:', error);\n }\n }\n currentUser = undefined;\n await Promise.all([\n fs.rm(getDevelopmentCodeSigningDirectory(), { recursive: true, force: true }),\n setSessionAsync(undefined),\n ]);\n Log.log('Logged out');\n}\n"],"names":["ANONYMOUS_USERNAME","browserLoginAsync","getActorDisplayName","getUserAsync","loginAsync","logoutAsync","debug","require","currentUser","user","__typename","username","firstName","getSession","hasCredentials","getAccessToken","sessionSecret","env","EXPO_OFFLINE","UserQuery","currentUserAsync","undefined","credentials","res","fetchAsync","method","body","JSON","stringify","json","data","userData","meUserActorAsync","setSessionAsync","userId","id","currentConnection","sso","getSessionUsingBrowserAuthFlowAsync","expoWebsiteUrl","getExpoWebsiteBaseUrl","error","Promise","all","fs","rm","getDevelopmentCodeSigningDirectory","recursive","force","Log","log"],"mappings":";;;;;;;;;;;QAeaA;eAAAA;;QAuDSC;eAAAA;;QAhDNC;eAAAA;;QAeMC;eAAAA;;QASAC;eAAAA;;QAwCAC;eAAAA;;;;yBAtFS;;;;;;8BAE6B;iCACR;6DAC/B;6BAC8B;qBAC/B;0BACkB;2BACA;wBACX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAE3B,MAAMC,QAAQC,QAAQ,SAAS;AAE/B,IAAIC;AAEG,MAAMR,qBAAqB;AAO3B,SAASE,oBAAoBO,IAAY;IAC9C,OAAQA,wBAAAA,KAAMC,UAAU;QACtB,KAAK;YACH,OAAOD,KAAKE,QAAQ;QACtB,KAAK;YACH,OAAOF,KAAKE,QAAQ;QACtB,KAAK;YACH,OAAOF,KAAKG,SAAS,GAAG,GAAGH,KAAKG,SAAS,CAAC,QAAQ,CAAC,GAAG;QACxD;YACE,OAAOZ;IACX;AACF;AAIO,eAAeG;QACuBU;IAA3C,MAAMC,iBAAiBC,IAAAA,4BAAc,SAAMF,cAAAA,IAAAA,wBAAU,wBAAVA,YAAcG,aAAa;IACtE,IAAI,CAACC,QAAG,CAACC,YAAY,IAAI,CAACV,eAAeM,gBAAgB;QACvD,MAAML,OAAO,MAAMU,oBAAS,CAACC,gBAAgB;QAC7CZ,cAAcC,QAAQY;IACxB;IACA,OAAOb;AACT;AAEO,eAAeJ,WAAWkB,WAIhC;IACC,MAAMC,MAAM,MAAMC,IAAAA,kBAAU,EAAC,mBAAmB;QAC9CC,QAAQ;QACRC,MAAMC,KAAKC,SAAS,CAACN;IACvB;IACA,MAAMO,OAAY,MAAMN,IAAIM,IAAI;IAChC,MAAMb,gBAAgBa,KAAKC,IAAI,CAACd,aAAa;IAE7C,MAAMe,WAAW,MAAMZ,oBAAS,CAACa,gBAAgB,CAAC;QAChD,gBAAgBhB;IAClB;IAEA,MAAMiB,IAAAA,6BAAe,EAAC;QACpBjB;QACAkB,QAAQH,SAASI,EAAE;QACnBxB,UAAUoB,SAASpB,QAAQ;QAC3ByB,mBAAmB;IACrB;AACF;AAEO,eAAenC,kBAAkB,EAAEoC,MAAM,KAAK,EAAE;IACrD,MAAMrB,gBAAgB,MAAMsB,IAAAA,oDAAmC,EAAC;QAC9DC,gBAAgBC,IAAAA,+BAAqB;QACrCH;IACF;IACA,MAAMN,WAAW,MAAMZ,oBAAS,CAACa,gBAAgB,CAAC;QAChD,gBAAgBhB;IAClB;IACA,MAAMiB,IAAAA,6BAAe,EAAC;QACpBjB;QACAkB,QAAQH,SAASI,EAAE;QACnBxB,UAAUoB,SAASpB,QAAQ;QAC3ByB,mBAAmB;IACrB;AACF;AAEO,eAAe/B;QACEQ;IAAtB,MAAMG,iBAAgBH,cAAAA,IAAAA,wBAAU,wBAAVA,YAAcG,aAAa;IACjD,IAAIA,eAAe;QACjB,IAAI;YACF,MAAMQ,IAAAA,kBAAU,EAAC,eAAe;gBAAEC,QAAQ;YAAO;QACnD,EAAE,OAAOgB,OAAO;YACdnC,MAAM,kDAAkDmC;QAC1D;IACF;IACAjC,cAAca;IACd,MAAMqB,QAAQC,GAAG,CAAC;QAChBC,cAAE,CAACC,EAAE,CAACC,IAAAA,+CAAkC,KAAI;YAAEC,WAAW;YAAMC,OAAO;QAAK;QAC3Ef,IAAAA,6BAAe,EAACZ;KACjB;IACD4B,KAAIC,GAAG,CAAC;AACV"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/customize/typescript.ts"],"sourcesContent":["import { getConfig } from '@expo/config';\n\nimport { Log } from '../log';\n\nexport async function typescript(projectRoot: string) {\n const { TypeScriptProjectPrerequisite } = await import(\n '../start/doctor/typescript/TypeScriptProjectPrerequisite.js'\n );\n const { MetroBundlerDevServer } = await import('../start/server/metro/MetroBundlerDevServer.js');\n const { getPlatformBundlers } = await import('../start/server/platformBundlers.js');\n\n try {\n await new TypeScriptProjectPrerequisite(projectRoot).bootstrapAsync();\n } catch (error: any) {\n // Ensure the process doesn't fail if the TypeScript check fails.\n // This could happen during the install.\n Log.log();\n Log.exception(error);\n return;\n }\n\n const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true });\n await new MetroBundlerDevServer(projectRoot, getPlatformBundlers(projectRoot, exp), {\n isDevClient: true,\n }).startTypeScriptServices();\n}\n"],"names":["typescript","projectRoot","TypeScriptProjectPrerequisite","MetroBundlerDevServer","getPlatformBundlers","bootstrapAsync","error","Log","log","exception","exp","getConfig","skipSDKVersionRequirement","isDevClient","startTypeScriptServices"],"mappings":";;;;+BAIsBA;;;eAAAA;;;;yBAJI;;;;;;qBAEN;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEb,eAAeA,WAAWC,WAAmB;IAClD,MAAM,EAAEC,6BAA6B,EAAE,GAAG,MAAM,mEAAA,QAC9C;IAEF,MAAM,EAAEC,qBAAqB,EAAE,GAAG,MAAM,mEAAA,QAAO;IAC/C,MAAM,EAAEC,mBAAmB,EAAE,GAAG,MAAM,mEAAA,QAAO;IAE7C,IAAI;QACF,MAAM,IAAIF,8BAA8BD,aAAaI,cAAc;IACrE,EAAE,OAAOC,OAAY;QACnB,iEAAiE;QACjE,wCAAwC;QACxCC,QAAG,CAACC,GAAG;QACPD,QAAG,CAACE,SAAS,CAACH;QACd;IACF;IAEA,MAAM,EAAEI,GAAG,EAAE,GAAGC,IAAAA,mBAAS,EAACV,aAAa;QAAEW,2BAA2B;IAAK;IACzE,MAAM,IAAIT,sBAAsBF,aAAaG,oBAAoBH,aAAaS,MAAM;QAClFG,aAAa;IACf,GAAGC,uBAAuB;AAC5B"}
1
+ {"version":3,"sources":["../../../src/customize/typescript.ts"],"sourcesContent":["import { getConfig } from '@expo/config';\n\nimport { Log } from '../log';\n\nexport async function typescript(projectRoot: string) {\n const { TypeScriptProjectPrerequisite } =\n await import('../start/doctor/typescript/TypeScriptProjectPrerequisite.js');\n const { MetroBundlerDevServer } = await import('../start/server/metro/MetroBundlerDevServer.js');\n const { getPlatformBundlers } = await import('../start/server/platformBundlers.js');\n\n try {\n await new TypeScriptProjectPrerequisite(projectRoot).bootstrapAsync();\n } catch (error: any) {\n // Ensure the process doesn't fail if the TypeScript check fails.\n // This could happen during the install.\n Log.log();\n Log.exception(error);\n return;\n }\n\n const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true });\n await new MetroBundlerDevServer(projectRoot, getPlatformBundlers(projectRoot, exp), {\n isDevClient: true,\n }).startTypeScriptServices();\n}\n"],"names":["typescript","projectRoot","TypeScriptProjectPrerequisite","MetroBundlerDevServer","getPlatformBundlers","bootstrapAsync","error","Log","log","exception","exp","getConfig","skipSDKVersionRequirement","isDevClient","startTypeScriptServices"],"mappings":";;;;+BAIsBA;;;eAAAA;;;;yBAJI;;;;;;qBAEN;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEb,eAAeA,WAAWC,WAAmB;IAClD,MAAM,EAAEC,6BAA6B,EAAE,GACrC,MAAM,mEAAA,QAAO;IACf,MAAM,EAAEC,qBAAqB,EAAE,GAAG,MAAM,mEAAA,QAAO;IAC/C,MAAM,EAAEC,mBAAmB,EAAE,GAAG,MAAM,mEAAA,QAAO;IAE7C,IAAI;QACF,MAAM,IAAIF,8BAA8BD,aAAaI,cAAc;IACrE,EAAE,OAAOC,OAAY;QACnB,iEAAiE;QACjE,wCAAwC;QACxCC,QAAG,CAACC,GAAG;QACPD,QAAG,CAACE,SAAS,CAACH;QACd;IACF;IAEA,MAAM,EAAEI,GAAG,EAAE,GAAGC,IAAAA,mBAAS,EAACV,aAAa;QAAEW,2BAA2B;IAAK;IACzE,MAAM,IAAIT,sBAAsBF,aAAaG,oBAAoBH,aAAaS,MAAM;QAClFG,aAAa;IACf,GAAGC,uBAAuB;AAC5B"}
@@ -76,7 +76,7 @@ function getInitMetadata() {
76
76
  return {
77
77
  format: 'v0-jsonl',
78
78
  // Version is added in the build script.
79
- version: "56.1.3" ?? 'UNVERSIONED'
79
+ version: "56.1.5" ?? 'UNVERSIONED'
80
80
  };
81
81
  }
82
82
  function getWellKnownTemporaryLogFile(projectRoot, command) {
@@ -88,6 +88,7 @@ const _xcrun = require("./xcrun");
88
88
  const _log = /*#__PURE__*/ _interop_require_wildcard(require("../../../log"));
89
89
  const _errors = require("../../../utils/errors");
90
90
  const _fn = require("../../../utils/fn");
91
+ const _link = require("../../../utils/link");
91
92
  const _plist = require("../../../utils/plist");
92
93
  const _profile = require("../../../utils/profile");
93
94
  function _interop_require_default(obj) {
@@ -305,6 +306,9 @@ async function bootDeviceAsync(device) {
305
306
  } catch (error) {
306
307
  var _error_stderr;
307
308
  if (!((_error_stderr = error.stderr) == null ? void 0 : _error_stderr.match(/Unable to boot device in current state: Booted/))) {
309
+ error.message += `\n${(0, _link.learnMore)('https://docs.expo.dev/workflow/ios-simulator/#troubleshooting', {
310
+ learnMoreMessage: 'Troubleshooting guide'
311
+ })}`;
308
312
  throw error;
309
313
  }
310
314
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/start/platforms/ios/simctl.ts"],"sourcesContent":["import type { SpawnOptions, SpawnResult } from '@expo/spawn-async';\nimport spawnAsync from '@expo/spawn-async';\nimport bplistCreator from 'bplist-creator';\nimport fs from 'fs';\nimport os from 'os';\nimport path from 'path';\n\nimport { isSpawnResultError, xcrunAsync } from './xcrun';\nimport * as Log from '../../../log';\nimport { CommandError } from '../../../utils/errors';\nimport { memoize } from '../../../utils/fn';\nimport { parsePlistAsync } from '../../../utils/plist';\nimport { profile } from '../../../utils/profile';\n\nconst debug = require('debug')('expo:simctl') as typeof console.log;\n\ntype DeviceState = 'Shutdown' | 'Booted';\n\nexport type OSType = 'iOS' | 'tvOS' | 'watchOS' | 'macOS' | 'xrOS';\n\nexport type Device = {\n availabilityError?: 'runtime profile not found';\n /** '/Users/name/Library/Developer/CoreSimulator/Devices/00E55DC0-0364-49DF-9EC6-77BE587137D4/data' */\n dataPath: string;\n /** @example `2811236352` */\n dataPathSize?: number;\n /** '/Users/name/Library/Logs/CoreSimulator/00E55DC0-0364-49DF-9EC6-77BE587137D4' */\n logPath: string;\n /** @example `479232` */\n logPathSize?: number;\n /** '00E55DC0-0364-49DF-9EC6-77BE587137D4' */\n udid: string;\n /** 'com.apple.CoreSimulator.SimRuntime.iOS-15-1' */\n runtime: string;\n /** If the device is \"available\" which generally means that the OS files haven't been deleted (this can happen when Xcode updates). */\n isAvailable: boolean;\n /** 'com.apple.CoreSimulator.SimDeviceType.iPhone-13-Pro' */\n deviceTypeIdentifier: string;\n state: DeviceState;\n /** 'iPhone 13 Pro' */\n name: string;\n /** Type of OS the device uses. */\n osType: OSType;\n /** '15.1' */\n osVersion: string;\n /** 'iPhone 13 Pro (15.1)' */\n windowName: string;\n};\n\ntype SimulatorDeviceList = {\n devices: {\n [runtime: string]: Device[];\n };\n};\n\ntype DeviceContext = Pick<Device, 'udid'>;\n\n/** Returns true if the given value is an `OSType`, if we don't recognize the value we continue anyways but warn. */\nexport function isOSType(value: any): value is OSType {\n if (!value || typeof value !== 'string') return false;\n\n const knownTypes = ['iOS', 'tvOS', 'watchOS', 'macOS'];\n if (!knownTypes.includes(value)) {\n Log.warn(`Unknown OS type: ${value}. Expected one of: ${knownTypes.join(', ')}`);\n }\n return true;\n}\n\n/**\n * Returns the local path for the installed tar.app. Returns null when the app isn't installed.\n *\n * @param device context for selecting a device.\n * @param props.appId bundle identifier for app.\n * @returns local file path to installed app binary, e.g. '/Users/evanbacon/Library/Developer/CoreSimulator/Devices/EFEEA6EF-E3F5-4EDE-9B72-29EAFA7514AE/data/Containers/Bundle/Application/FA43A0C6-C2AD-442D-B8B1-EAF3E88CF3BF/Exponent-2.21.3.tar.app'\n */\nexport async function getContainerPathAsync(\n device: Partial<DeviceContext>,\n {\n appId,\n }: {\n appId: string;\n }\n): Promise<string | null> {\n try {\n const { stdout } = await simctlAsync(['get_app_container', resolveId(device), appId]);\n return stdout.trim();\n } catch (error: any) {\n if (error.stderr?.match(/No such file or directory/)) {\n return null;\n }\n throw error;\n }\n}\n\n/** Return a value from an installed app's Info.plist. */\nexport async function getInfoPlistValueAsync(\n device: Partial<DeviceContext>,\n {\n appId,\n key,\n containerPath,\n }: {\n appId: string;\n key: string;\n containerPath?: string;\n }\n): Promise<string | null> {\n const ensuredContainerPath = containerPath ?? (await getContainerPathAsync(device, { appId }));\n if (ensuredContainerPath) {\n try {\n const { output } = await spawnAsync(\n 'defaults',\n ['read', `${ensuredContainerPath}/Info`, key],\n {\n stdio: 'pipe',\n }\n );\n return output.join('\\n').trim();\n } catch {\n return null;\n }\n }\n return null;\n}\n\n/** Rewrite the simulator permissions to allow opening deep links without needing to prompt the user first. */\nasync function updateSimulatorLinkingPermissionsAsync(\n device: Partial<DeviceContext>,\n { url, appId }: { url: string; appId?: string }\n) {\n if (!device.udid || !appId) {\n debug('Skipping deep link permissions as missing properties could not be found:', {\n url,\n appId,\n udid: device.udid,\n });\n return;\n }\n debug('Rewriting simulator permissions to support deep linking:', {\n url,\n appId,\n udid: device.udid,\n });\n let scheme: string;\n try {\n // Attempt to extract the scheme from the URL.\n scheme = new URL(url).protocol.slice(0, -1);\n } catch (error: any) {\n debug(`Could not parse the URL scheme: ${error.message}`);\n return;\n }\n\n // Get the hard-coded path to the simulator's scheme approval plist file.\n const plistPath = path.join(\n os.homedir(),\n `Library/Developer/CoreSimulator/Devices`,\n device.udid,\n `data/Library/Preferences/com.apple.launchservices.schemeapproval.plist`\n );\n\n const plistData = fs.existsSync(plistPath)\n ? // If the file exists, then read it in the bplist format.\n await parsePlistAsync(plistPath)\n : // The file doesn't exist when we first launch the simulator, but an empty object can be used to create it (June 2024 x Xcode 15.3).\n // Can be tested by launching a new simulator or by deleting the file and relaunching the simulator.\n {};\n\n debug('Allowed links:', plistData);\n const key = `com.apple.CoreSimulator.CoreSimulatorBridge-->${scheme}`;\n // Replace any existing value for the scheme with the new appId.\n plistData[key] = appId;\n debug('Allowing deep link:', { key, appId });\n\n try {\n const data = bplistCreator(plistData);\n // Write the updated plist back to disk\n await fs.promises.writeFile(plistPath, data);\n } catch (error: any) {\n Log.warn(`Could not update simulator linking permissions: ${error.message}`);\n }\n}\n\nconst updateSimulatorLinkingPermissionsAsyncMemo = memoize(updateSimulatorLinkingPermissionsAsync);\n\n/** Open a URL on a device. The url can have any protocol. */\nexport async function openUrlAsync(\n device: Partial<DeviceContext>,\n options: { url: string; appId?: string }\n): Promise<void> {\n if (options.appId) {\n await profile(\n updateSimulatorLinkingPermissionsAsyncMemo,\n 'updateSimulatorLinkingPermissionsAsync'\n )({ udid: device.udid }, options);\n }\n\n try {\n // Skip logging since this is likely to fail.\n await simctlAsync(['openurl', resolveId(device), options.url]);\n } catch (error: any) {\n if (!error.stderr?.match(/Unable to lookup in current state: Shut/)) {\n throw error;\n }\n\n // If the device was in a weird in-between state (\"Shutting Down\" or \"Shutdown\"), then attempt to reboot it and try again.\n // This can happen when quitting the Simulator app, and immediately pressing `i` to reopen the project.\n\n // First boot the simulator\n await bootDeviceAsync({ udid: resolveId(device) });\n\n // Finally, try again...\n return await openUrlAsync(device, options);\n }\n}\n\n/** Open a simulator using a bundle identifier. If no app with a matching bundle identifier is installed then an error will be thrown. */\nexport async function openAppIdAsync(\n device: Partial<DeviceContext>,\n options: {\n appId: string;\n }\n): Promise<SpawnResult> {\n const results = await openAppIdInternalAsync(device, options);\n // Similar to 194, this is a conformance issue which indicates that the given device has no app that can handle our launch request.\n if (results.status === 4) {\n throw new CommandError('APP_NOT_INSTALLED', results.stderr);\n }\n return results;\n}\nasync function openAppIdInternalAsync(\n device: Partial<DeviceContext>,\n options: {\n appId: string;\n }\n): Promise<SpawnResult> {\n try {\n return await simctlAsync(['launch', resolveId(device), options.appId]);\n } catch (error: any) {\n if ('status' in error) {\n return error;\n }\n throw error;\n }\n}\n\n// This will only boot in headless mode if the Simulator app is not running.\nexport async function bootAsync(device: DeviceContext): Promise<Device | null> {\n await bootDeviceAsync(device);\n return isDeviceBootedAsync(device);\n}\n\n/** Returns a list of devices whose current state is 'Booted' as an array. */\nexport async function getBootedSimulatorsAsync(): Promise<Device[]> {\n const simulatorDeviceInfo = await getRuntimesAsync('devices');\n return Object.values(simulatorDeviceInfo.devices).flatMap((runtime) =>\n runtime.filter((device) => device.state === 'Booted')\n );\n}\n\n/** Returns the current device if its state is 'Booted'. */\nexport async function isDeviceBootedAsync(device: Partial<DeviceContext>): Promise<Device | null> {\n // Simulators can be booted even if the app isn't running :(\n const devices = await getBootedSimulatorsAsync();\n if (device.udid) {\n return devices.find((bootedDevice) => bootedDevice.udid === device.udid) ?? null;\n }\n\n return devices[0] ?? null;\n}\n\n/** Boot a device. */\nexport async function bootDeviceAsync(device: DeviceContext): Promise<void> {\n try {\n // Skip logging since this is likely to fail.\n await simctlAsync(['boot', device.udid]);\n } catch (error: any) {\n if (!error.stderr?.match(/Unable to boot device in current state: Booted/)) {\n throw error;\n }\n }\n}\n\n/** Install a binary file on the device. */\nexport async function installAsync(\n device: Partial<DeviceContext>,\n options: {\n /** Local absolute file path to an app binary that is built and provisioned for iOS simulators. */\n filePath: string;\n }\n): Promise<any> {\n return simctlAsync(['install', resolveId(device), options.filePath]);\n}\n\n/** Uninstall an app from the provided device. */\nexport async function uninstallAsync(\n device: Partial<DeviceContext>,\n options: {\n /** Bundle identifier */\n appId: string;\n }\n): Promise<any> {\n return simctlAsync(['uninstall', resolveId(device), options.appId]);\n}\n\nfunction parseSimControlJSONResults(input: string): any {\n try {\n return JSON.parse(input);\n } catch (error: any) {\n // Nov 15, 2020: Observed this can happen when opening the simulator and the simulator prompts the user to update the xcode command line tools.\n // Unexpected token I in JSON at position 0\n if (error.message.includes('Unexpected token')) {\n Log.error(`Apple's simctl returned malformed JSON:\\n${input}`);\n }\n throw error;\n }\n}\n\n/** Get all runtime devices given a certain type. */\nasync function getRuntimesAsync(\n type: 'devices' | 'devicetypes' | 'runtimes' | 'pairs',\n query?: string | 'available'\n): Promise<SimulatorDeviceList> {\n const result = await simctlAsync(['list', type, '--json', query]);\n const info = parseSimControlJSONResults(result.stdout) as SimulatorDeviceList;\n\n for (const runtime of Object.keys(info.devices)) {\n // Given a string like 'com.apple.CoreSimulator.SimRuntime.tvOS-13-4'\n const runtimeSuffix = runtime.split('com.apple.CoreSimulator.SimRuntime.').pop()!;\n // Create an array [tvOS, 13, 4]\n const [osType, ...osVersionComponents] = runtimeSuffix.split('-');\n // Join the end components [13, 4] -> '13.4'\n const osVersion = osVersionComponents.join('.');\n const sims = info.devices[runtime];\n if (sims) {\n for (const device of sims) {\n device.runtime = runtime;\n device.osVersion = osVersion;\n device.windowName = `${device.name} (${osVersion})`;\n device.osType = osType as OSType;\n }\n }\n }\n return info;\n}\n\n/** Return a list of iOS simulators. */\nexport async function getDevicesAsync(): Promise<Device[]> {\n const simulatorDeviceInfo = await getRuntimesAsync('devices');\n return Object.values(simulatorDeviceInfo.devices).flat();\n}\n\n/** Run a `simctl` command. */\nexport async function simctlAsync(\n args: (string | undefined)[],\n options?: SpawnOptions\n): Promise<SpawnResult> {\n try {\n return await xcrunAsync(['simctl', ...args], options);\n } catch (error) {\n if (isSpawnResultError(error)) {\n // TODO: Add more tips.\n // if (error.status === 115) {\n // }\n }\n throw error;\n }\n}\n\nfunction resolveId(device: Partial<DeviceContext>): string {\n return device.udid ?? 'booted';\n}\n"],"names":["bootAsync","bootDeviceAsync","getBootedSimulatorsAsync","getContainerPathAsync","getDevicesAsync","getInfoPlistValueAsync","installAsync","isDeviceBootedAsync","isOSType","openAppIdAsync","openUrlAsync","simctlAsync","uninstallAsync","debug","require","value","knownTypes","includes","Log","warn","join","device","appId","stdout","resolveId","trim","error","stderr","match","key","containerPath","ensuredContainerPath","output","spawnAsync","stdio","updateSimulatorLinkingPermissionsAsync","url","udid","scheme","URL","protocol","slice","message","plistPath","path","os","homedir","plistData","fs","existsSync","parsePlistAsync","data","bplistCreator","promises","writeFile","updateSimulatorLinkingPermissionsAsyncMemo","memoize","options","profile","results","openAppIdInternalAsync","status","CommandError","simulatorDeviceInfo","getRuntimesAsync","Object","values","devices","flatMap","runtime","filter","state","find","bootedDevice","filePath","parseSimControlJSONResults","input","JSON","parse","type","query","result","info","keys","runtimeSuffix","split","pop","osType","osVersionComponents","osVersion","sims","windowName","name","flat","args","xcrunAsync","isSpawnResultError"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../src/start/platforms/ios/simctl.ts"],"sourcesContent":["import type { SpawnOptions, SpawnResult } from '@expo/spawn-async';\nimport spawnAsync from '@expo/spawn-async';\nimport bplistCreator from 'bplist-creator';\nimport fs from 'fs';\nimport os from 'os';\nimport path from 'path';\n\nimport { isSpawnResultError, xcrunAsync } from './xcrun';\nimport * as Log from '../../../log';\nimport { CommandError } from '../../../utils/errors';\nimport { memoize } from '../../../utils/fn';\nimport { learnMore } from '../../../utils/link';\nimport { parsePlistAsync } from '../../../utils/plist';\nimport { profile } from '../../../utils/profile';\n\nconst debug = require('debug')('expo:simctl') as typeof console.log;\n\ntype DeviceState = 'Shutdown' | 'Booted';\n\nexport type OSType = 'iOS' | 'tvOS' | 'watchOS' | 'macOS' | 'xrOS';\n\nexport type Device = {\n availabilityError?: 'runtime profile not found';\n /** '/Users/name/Library/Developer/CoreSimulator/Devices/00E55DC0-0364-49DF-9EC6-77BE587137D4/data' */\n dataPath: string;\n /** @example `2811236352` */\n dataPathSize?: number;\n /** '/Users/name/Library/Logs/CoreSimulator/00E55DC0-0364-49DF-9EC6-77BE587137D4' */\n logPath: string;\n /** @example `479232` */\n logPathSize?: number;\n /** '00E55DC0-0364-49DF-9EC6-77BE587137D4' */\n udid: string;\n /** 'com.apple.CoreSimulator.SimRuntime.iOS-15-1' */\n runtime: string;\n /** If the device is \"available\" which generally means that the OS files haven't been deleted (this can happen when Xcode updates). */\n isAvailable: boolean;\n /** 'com.apple.CoreSimulator.SimDeviceType.iPhone-13-Pro' */\n deviceTypeIdentifier: string;\n state: DeviceState;\n /** 'iPhone 13 Pro' */\n name: string;\n /** Type of OS the device uses. */\n osType: OSType;\n /** '15.1' */\n osVersion: string;\n /** 'iPhone 13 Pro (15.1)' */\n windowName: string;\n};\n\ntype SimulatorDeviceList = {\n devices: {\n [runtime: string]: Device[];\n };\n};\n\ntype DeviceContext = Pick<Device, 'udid'>;\n\n/** Returns true if the given value is an `OSType`, if we don't recognize the value we continue anyways but warn. */\nexport function isOSType(value: any): value is OSType {\n if (!value || typeof value !== 'string') return false;\n\n const knownTypes = ['iOS', 'tvOS', 'watchOS', 'macOS'];\n if (!knownTypes.includes(value)) {\n Log.warn(`Unknown OS type: ${value}. Expected one of: ${knownTypes.join(', ')}`);\n }\n return true;\n}\n\n/**\n * Returns the local path for the installed tar.app. Returns null when the app isn't installed.\n *\n * @param device context for selecting a device.\n * @param props.appId bundle identifier for app.\n * @returns local file path to installed app binary, e.g. '/Users/evanbacon/Library/Developer/CoreSimulator/Devices/EFEEA6EF-E3F5-4EDE-9B72-29EAFA7514AE/data/Containers/Bundle/Application/FA43A0C6-C2AD-442D-B8B1-EAF3E88CF3BF/Exponent-2.21.3.tar.app'\n */\nexport async function getContainerPathAsync(\n device: Partial<DeviceContext>,\n {\n appId,\n }: {\n appId: string;\n }\n): Promise<string | null> {\n try {\n const { stdout } = await simctlAsync(['get_app_container', resolveId(device), appId]);\n return stdout.trim();\n } catch (error: any) {\n if (error.stderr?.match(/No such file or directory/)) {\n return null;\n }\n throw error;\n }\n}\n\n/** Return a value from an installed app's Info.plist. */\nexport async function getInfoPlistValueAsync(\n device: Partial<DeviceContext>,\n {\n appId,\n key,\n containerPath,\n }: {\n appId: string;\n key: string;\n containerPath?: string;\n }\n): Promise<string | null> {\n const ensuredContainerPath = containerPath ?? (await getContainerPathAsync(device, { appId }));\n if (ensuredContainerPath) {\n try {\n const { output } = await spawnAsync(\n 'defaults',\n ['read', `${ensuredContainerPath}/Info`, key],\n {\n stdio: 'pipe',\n }\n );\n return output.join('\\n').trim();\n } catch {\n return null;\n }\n }\n return null;\n}\n\n/** Rewrite the simulator permissions to allow opening deep links without needing to prompt the user first. */\nasync function updateSimulatorLinkingPermissionsAsync(\n device: Partial<DeviceContext>,\n { url, appId }: { url: string; appId?: string }\n) {\n if (!device.udid || !appId) {\n debug('Skipping deep link permissions as missing properties could not be found:', {\n url,\n appId,\n udid: device.udid,\n });\n return;\n }\n debug('Rewriting simulator permissions to support deep linking:', {\n url,\n appId,\n udid: device.udid,\n });\n let scheme: string;\n try {\n // Attempt to extract the scheme from the URL.\n scheme = new URL(url).protocol.slice(0, -1);\n } catch (error: any) {\n debug(`Could not parse the URL scheme: ${error.message}`);\n return;\n }\n\n // Get the hard-coded path to the simulator's scheme approval plist file.\n const plistPath = path.join(\n os.homedir(),\n `Library/Developer/CoreSimulator/Devices`,\n device.udid,\n `data/Library/Preferences/com.apple.launchservices.schemeapproval.plist`\n );\n\n const plistData = fs.existsSync(plistPath)\n ? // If the file exists, then read it in the bplist format.\n await parsePlistAsync(plistPath)\n : // The file doesn't exist when we first launch the simulator, but an empty object can be used to create it (June 2024 x Xcode 15.3).\n // Can be tested by launching a new simulator or by deleting the file and relaunching the simulator.\n {};\n\n debug('Allowed links:', plistData);\n const key = `com.apple.CoreSimulator.CoreSimulatorBridge-->${scheme}`;\n // Replace any existing value for the scheme with the new appId.\n plistData[key] = appId;\n debug('Allowing deep link:', { key, appId });\n\n try {\n const data = bplistCreator(plistData);\n // Write the updated plist back to disk\n await fs.promises.writeFile(plistPath, data);\n } catch (error: any) {\n Log.warn(`Could not update simulator linking permissions: ${error.message}`);\n }\n}\n\nconst updateSimulatorLinkingPermissionsAsyncMemo = memoize(updateSimulatorLinkingPermissionsAsync);\n\n/** Open a URL on a device. The url can have any protocol. */\nexport async function openUrlAsync(\n device: Partial<DeviceContext>,\n options: { url: string; appId?: string }\n): Promise<void> {\n if (options.appId) {\n await profile(\n updateSimulatorLinkingPermissionsAsyncMemo,\n 'updateSimulatorLinkingPermissionsAsync'\n )({ udid: device.udid }, options);\n }\n\n try {\n // Skip logging since this is likely to fail.\n await simctlAsync(['openurl', resolveId(device), options.url]);\n } catch (error: any) {\n if (!error.stderr?.match(/Unable to lookup in current state: Shut/)) {\n throw error;\n }\n\n // If the device was in a weird in-between state (\"Shutting Down\" or \"Shutdown\"), then attempt to reboot it and try again.\n // This can happen when quitting the Simulator app, and immediately pressing `i` to reopen the project.\n\n // First boot the simulator\n await bootDeviceAsync({ udid: resolveId(device) });\n\n // Finally, try again...\n return await openUrlAsync(device, options);\n }\n}\n\n/** Open a simulator using a bundle identifier. If no app with a matching bundle identifier is installed then an error will be thrown. */\nexport async function openAppIdAsync(\n device: Partial<DeviceContext>,\n options: {\n appId: string;\n }\n): Promise<SpawnResult> {\n const results = await openAppIdInternalAsync(device, options);\n // Similar to 194, this is a conformance issue which indicates that the given device has no app that can handle our launch request.\n if (results.status === 4) {\n throw new CommandError('APP_NOT_INSTALLED', results.stderr);\n }\n return results;\n}\nasync function openAppIdInternalAsync(\n device: Partial<DeviceContext>,\n options: {\n appId: string;\n }\n): Promise<SpawnResult> {\n try {\n return await simctlAsync(['launch', resolveId(device), options.appId]);\n } catch (error: any) {\n if ('status' in error) {\n return error;\n }\n throw error;\n }\n}\n\n// This will only boot in headless mode if the Simulator app is not running.\nexport async function bootAsync(device: DeviceContext): Promise<Device | null> {\n await bootDeviceAsync(device);\n return isDeviceBootedAsync(device);\n}\n\n/** Returns a list of devices whose current state is 'Booted' as an array. */\nexport async function getBootedSimulatorsAsync(): Promise<Device[]> {\n const simulatorDeviceInfo = await getRuntimesAsync('devices');\n return Object.values(simulatorDeviceInfo.devices).flatMap((runtime) =>\n runtime.filter((device) => device.state === 'Booted')\n );\n}\n\n/** Returns the current device if its state is 'Booted'. */\nexport async function isDeviceBootedAsync(device: Partial<DeviceContext>): Promise<Device | null> {\n // Simulators can be booted even if the app isn't running :(\n const devices = await getBootedSimulatorsAsync();\n if (device.udid) {\n return devices.find((bootedDevice) => bootedDevice.udid === device.udid) ?? null;\n }\n\n return devices[0] ?? null;\n}\n\n/** Boot a device. */\nexport async function bootDeviceAsync(device: DeviceContext): Promise<void> {\n try {\n // Skip logging since this is likely to fail.\n await simctlAsync(['boot', device.udid]);\n } catch (error: any) {\n if (!error.stderr?.match(/Unable to boot device in current state: Booted/)) {\n error.message += `\\n${learnMore('https://docs.expo.dev/workflow/ios-simulator/#troubleshooting', { learnMoreMessage: 'Troubleshooting guide' })}`;\n throw error;\n }\n }\n}\n\n/** Install a binary file on the device. */\nexport async function installAsync(\n device: Partial<DeviceContext>,\n options: {\n /** Local absolute file path to an app binary that is built and provisioned for iOS simulators. */\n filePath: string;\n }\n): Promise<any> {\n return simctlAsync(['install', resolveId(device), options.filePath]);\n}\n\n/** Uninstall an app from the provided device. */\nexport async function uninstallAsync(\n device: Partial<DeviceContext>,\n options: {\n /** Bundle identifier */\n appId: string;\n }\n): Promise<any> {\n return simctlAsync(['uninstall', resolveId(device), options.appId]);\n}\n\nfunction parseSimControlJSONResults(input: string): any {\n try {\n return JSON.parse(input);\n } catch (error: any) {\n // Nov 15, 2020: Observed this can happen when opening the simulator and the simulator prompts the user to update the xcode command line tools.\n // Unexpected token I in JSON at position 0\n if (error.message.includes('Unexpected token')) {\n Log.error(`Apple's simctl returned malformed JSON:\\n${input}`);\n }\n throw error;\n }\n}\n\n/** Get all runtime devices given a certain type. */\nasync function getRuntimesAsync(\n type: 'devices' | 'devicetypes' | 'runtimes' | 'pairs',\n query?: string | 'available'\n): Promise<SimulatorDeviceList> {\n const result = await simctlAsync(['list', type, '--json', query]);\n const info = parseSimControlJSONResults(result.stdout) as SimulatorDeviceList;\n\n for (const runtime of Object.keys(info.devices)) {\n // Given a string like 'com.apple.CoreSimulator.SimRuntime.tvOS-13-4'\n const runtimeSuffix = runtime.split('com.apple.CoreSimulator.SimRuntime.').pop()!;\n // Create an array [tvOS, 13, 4]\n const [osType, ...osVersionComponents] = runtimeSuffix.split('-');\n // Join the end components [13, 4] -> '13.4'\n const osVersion = osVersionComponents.join('.');\n const sims = info.devices[runtime];\n if (sims) {\n for (const device of sims) {\n device.runtime = runtime;\n device.osVersion = osVersion;\n device.windowName = `${device.name} (${osVersion})`;\n device.osType = osType as OSType;\n }\n }\n }\n return info;\n}\n\n/** Return a list of iOS simulators. */\nexport async function getDevicesAsync(): Promise<Device[]> {\n const simulatorDeviceInfo = await getRuntimesAsync('devices');\n return Object.values(simulatorDeviceInfo.devices).flat();\n}\n\n/** Run a `simctl` command. */\nexport async function simctlAsync(\n args: (string | undefined)[],\n options?: SpawnOptions\n): Promise<SpawnResult> {\n try {\n return await xcrunAsync(['simctl', ...args], options);\n } catch (error) {\n if (isSpawnResultError(error)) {\n // TODO: Add more tips.\n // if (error.status === 115) {\n // }\n }\n throw error;\n }\n}\n\nfunction resolveId(device: Partial<DeviceContext>): string {\n return device.udid ?? 'booted';\n}\n"],"names":["bootAsync","bootDeviceAsync","getBootedSimulatorsAsync","getContainerPathAsync","getDevicesAsync","getInfoPlistValueAsync","installAsync","isDeviceBootedAsync","isOSType","openAppIdAsync","openUrlAsync","simctlAsync","uninstallAsync","debug","require","value","knownTypes","includes","Log","warn","join","device","appId","stdout","resolveId","trim","error","stderr","match","key","containerPath","ensuredContainerPath","output","spawnAsync","stdio","updateSimulatorLinkingPermissionsAsync","url","udid","scheme","URL","protocol","slice","message","plistPath","path","os","homedir","plistData","fs","existsSync","parsePlistAsync","data","bplistCreator","promises","writeFile","updateSimulatorLinkingPermissionsAsyncMemo","memoize","options","profile","results","openAppIdInternalAsync","status","CommandError","simulatorDeviceInfo","getRuntimesAsync","Object","values","devices","flatMap","runtime","filter","state","find","bootedDevice","learnMore","learnMoreMessage","filePath","parseSimControlJSONResults","input","JSON","parse","type","query","result","info","keys","runtimeSuffix","split","pop","osType","osVersionComponents","osVersion","sims","windowName","name","flat","args","xcrunAsync","isSpawnResultError"],"mappings":"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"}