@expo/cli 55.0.5 → 55.0.7

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 (28) hide show
  1. package/build/bin/cli +1 -1
  2. package/build/src/api/user/actions.js +5 -2
  3. package/build/src/api/user/actions.js.map +1 -1
  4. package/build/src/api/user/expoSsoLauncher.js +22 -49
  5. package/build/src/api/user/expoSsoLauncher.js.map +1 -1
  6. package/build/src/api/user/user.js +6 -5
  7. package/build/src/api/user/user.js.map +1 -1
  8. package/build/src/login/index.js +6 -2
  9. package/build/src/login/index.js.map +1 -1
  10. package/build/src/start/platforms/android/AndroidSdk.js +15 -6
  11. package/build/src/start/platforms/android/AndroidSdk.js.map +1 -1
  12. package/build/src/start/platforms/ios/devicectl.js +4 -1
  13. package/build/src/start/platforms/ios/devicectl.js.map +1 -1
  14. package/build/src/start/server/metro/MetroBundlerDevServer.js +1 -0
  15. package/build/src/start/server/metro/MetroBundlerDevServer.js.map +1 -1
  16. package/build/src/start/server/metro/debugging/createDebugMiddleware.js +5 -1
  17. package/build/src/start/server/metro/debugging/createDebugMiddleware.js.map +1 -1
  18. package/build/src/start/server/metro/instantiateMetro.js +10 -5
  19. package/build/src/start/server/metro/instantiateMetro.js.map +1 -1
  20. package/build/src/start/server/webpack/WebpackBundlerDevServer.js +2 -1
  21. package/build/src/start/server/webpack/WebpackBundlerDevServer.js.map +1 -1
  22. package/build/src/utils/downloadExpoGoAsync.js +36 -0
  23. package/build/src/utils/downloadExpoGoAsync.js.map +1 -1
  24. package/build/src/utils/env.js +13 -11
  25. package/build/src/utils/env.js.map +1 -1
  26. package/build/src/utils/telemetry/clients/FetchClient.js +1 -1
  27. package/build/src/utils/telemetry/utils/context.js +1 -1
  28. package/package.json +9 -9
package/build/bin/cli CHANGED
@@ -123,7 +123,7 @@ const args = (0, _arg().default)({
123
123
  });
124
124
  if (args['--version']) {
125
125
  // Version is added in the build script.
126
- console.log("55.0.5");
126
+ console.log("55.0.7");
127
127
  process.exit(0);
128
128
  }
129
129
  if (args['--non-interactive']) {
@@ -90,11 +90,14 @@ async function showLoginPromptAsync({ printNewLine = false, otp, ...options } =
90
90
  }
91
91
  const hasCredentials = options.username && options.password;
92
92
  const sso = options.sso;
93
+ const browser = options.browser;
93
94
  if (printNewLine) {
94
95
  _log.log();
95
96
  }
96
- if (sso) {
97
- await (0, _user.ssoLoginAsync)();
97
+ if (sso || browser) {
98
+ await (0, _user.browserLoginAsync)({
99
+ sso: !!sso
100
+ });
98
101
  return;
99
102
  }
100
103
  _log.log(hasCredentials ? `Logging in to EAS with email or username (exit and run 'npx expo login --help' for other login options)` : `Log in to EAS with email or username (exit and run 'npx expo login --help' for other login options)`);
@@ -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 { Actor, getUserAsync, loginAsync, ssoLoginAsync } from './user';\nimport * as Log from '../../log';\nimport { env } from '../../utils/env';\nimport { CommandError } from '../../utils/errors';\nimport { learnMore } from '../../utils/link';\nimport promptAsync, { confirmAsync, Question, 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} = {}): 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\n if (printNewLine) {\n Log.log();\n }\n\n if (sso) {\n await ssoLoginAsync();\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","Log","log","ssoLoginAsync","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":";;;;;;;;;;;IAasBA,oBAAoB;eAApBA;;IAgFAC,eAAe;eAAfA;;;;gEA7FH;;;;;;;gEACD;;;;;;qBAEoC;sBACS;6DAC1C;qBACD;wBACS;sBACH;iEACuC;wBACtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAGpB,eAAeD,qBAAqB,EACzCE,eAAe,KAAK,EACpBC,GAAG,EACH,GAAGC,SAOJ,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;IAEvB,IAAIT,cAAc;QAChBU,KAAIC,GAAG;IACT;IAEA,IAAIF,KAAK;QACP,MAAMG,IAAAA,mBAAa;QACnB;IACF;IAEAF,KAAIC,GAAG,CACLL,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,MAAMO,WAAW,MAAMC,IAAAA,gBAAW,EAChC;YACE,CAACZ,QAAQK,QAAQ,IAAI;gBACnBQ,MAAM;gBACNC,MAAM;gBACNC,SAAS;YACX;YACA,CAACf,QAAQM,QAAQ,IAAI;gBACnBO,MAAM;gBACNC,MAAM;gBACNC,SAAS;YACX;SACD,CAACC,MAAM,CAACC,UACT;YACEC,oBAAoB,CAAC,+DAA+D,EAAEC,IAAAA,eAAS,EAC7F,uDACA,CAAC,CAAC;QACN;QAEFd,aAAaM,SAASN,QAAQ;QAC9BC,aAAaK,SAASL,QAAQ;IAChC;IACA,8BAA8B;IAC9Bc,IAAAA,iBAAM,EAACf,YAAYC;IAEnB,IAAI;QACF,MAAMe,IAAAA,gBAAU,EAAC;YACfhB;YACAC;YACAP;QACF;IACF,EAAE,OAAOuB,GAAG;QACV,IAAIA,aAAaC,kBAAU,IAAID,EAAEE,kBAAkB,KAAK,8BAA8B;YACpF,MAAMC,IAAAA,0CAAqC,EACzCpB,UACAC,UACAgB,EAAEI,sBAAsB;QAE5B,OAAO;YACL,MAAMJ;QACR;IACF;AACF;AAEO,eAAezB;IACpB,MAAM8B,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,MAAMpC,qBAAqB;YAAEE,cAAc;QAAK;QAChD,OAAO,AAAC,MAAM8B,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 { Actor, 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 promptAsync, { confirmAsync, Question, 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":";;;;;;;;;;;IAasBA,oBAAoB;eAApBA;;IAkFAC,eAAe;eAAfA;;;;gEA/FH;;;;;;;gEACD;;;;;;qBAEoC;sBACa;6DAC9C;qBACD;wBACS;sBACH;iEACuC;wBACtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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"}
@@ -83,55 +83,37 @@ function _interop_require_wildcard(obj, nodeInterop) {
83
83
  }
84
84
  return newObj;
85
85
  }
86
- const successBody = `
87
- <!DOCTYPE html>
88
- <html lang="en">
89
- <head>
90
- <title>Expo SSO Login</title>
91
- <meta charset="utf-8">
92
- <style type="text/css">
93
- html {
94
- margin: 0;
95
- padding: 0
96
- }
97
-
98
- body {
99
- background-color: #fff;
100
- font-family: Tahoma,Verdana;
101
- font-size: 16px;
102
- color: #000;
103
- max-width: 100%;
104
- box-sizing: border-box;
105
- padding: .5rem;
106
- margin: 1em;
107
- overflow-wrap: break-word
108
- }
109
- </style>
110
- </head>
111
- <body>
112
- SSO login complete. You may now close this tab and return to the command prompt.
113
- </body>
114
- </html>`;
115
- async function getSessionUsingBrowserAuthFlowAsync({ expoWebsiteUrl }) {
86
+ async function getSessionUsingBrowserAuthFlowAsync({ expoWebsiteUrl, sso = false }) {
116
87
  const scheme = 'http';
117
88
  const hostname = 'localhost';
118
89
  const path = '/auth/callback';
119
- const buildExpoSsoLoginUrl = (port)=>{
120
- const data = {
90
+ const buildExpoLoginUrl = (port, sso)=>{
91
+ const params = _querystring().default.stringify({
92
+ confirm_account: 'true',
121
93
  app_redirect_uri: `${scheme}://${hostname}:${port}${path}`
122
- };
123
- const params = _querystring().default.stringify(data);
124
- return `${expoWebsiteUrl}/sso-login?${params}`;
94
+ });
95
+ return `${expoWebsiteUrl}${sso ? '/sso-login' : '/login'}?${params}`;
125
96
  };
126
97
  // Start server and begin auth flow
127
98
  const executeAuthFlow = ()=>{
128
99
  return new Promise(async (resolve, reject)=>{
129
100
  const connections = new Set();
130
101
  const server = _http().default.createServer((request, response)=>{
102
+ const redirectAndCleanup = (result)=>{
103
+ const redirectUrl = `${expoWebsiteUrl}/oauth/expo-cli?result=${result}`;
104
+ response.writeHead(302, {
105
+ Location: redirectUrl
106
+ });
107
+ response.end();
108
+ server.close();
109
+ for (const connection of connections){
110
+ connection.destroy();
111
+ }
112
+ };
131
113
  try {
132
114
  var _request_url;
133
- if (!(request.method === 'GET' && ((_request_url = request.url) == null ? void 0 : _request_url.includes(path)))) {
134
- throw new Error('Unexpected SSO login response.');
115
+ if (!(request.method === 'GET' && ((_request_url = request.url) == null ? void 0 : _request_url.includes('/auth/callback')))) {
116
+ throw new Error('Unexpected login response.');
135
117
  }
136
118
  const url = new URL(request.url, `http:${request.headers.host}`);
137
119
  const sessionSecret = url.searchParams.get('session_secret');
@@ -139,19 +121,10 @@ async function getSessionUsingBrowserAuthFlowAsync({ expoWebsiteUrl }) {
139
121
  throw new Error('Request missing session_secret search parameter.');
140
122
  }
141
123
  resolve(sessionSecret);
142
- response.writeHead(200, {
143
- 'Content-Type': 'text/html'
144
- });
145
- response.write(successBody);
146
- response.end();
124
+ redirectAndCleanup('success');
147
125
  } catch (error) {
126
+ redirectAndCleanup('error');
148
127
  reject(error);
149
- } finally{
150
- server.close();
151
- // Ensure that the server shuts down
152
- for (const connection of connections){
153
- connection.destroy();
154
- }
155
128
  }
156
129
  });
157
130
  server.listen(0, hostname, ()=>{
@@ -159,7 +132,7 @@ async function getSessionUsingBrowserAuthFlowAsync({ expoWebsiteUrl }) {
159
132
  const address = server.address();
160
133
  (0, _assert().default)(address !== null && typeof address === 'object', 'Server address and port should be set after listening has begun');
161
134
  const port = address.port;
162
- const authorizeUrl = buildExpoSsoLoginUrl(port);
135
+ const authorizeUrl = buildExpoLoginUrl(port, sso);
163
136
  (0, _betteropn().default)(authorizeUrl);
164
137
  });
165
138
  server.on('connection', (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 { Socket } from 'node:net';\nimport querystring from 'querystring';\n\nimport * as Log from '../../log';\n\nconst successBody = `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <title>Expo SSO Login</title>\n <meta charset=\"utf-8\">\n <style type=\"text/css\">\n html {\n margin: 0;\n padding: 0\n }\n\n body {\n background-color: #fff;\n font-family: Tahoma,Verdana;\n font-size: 16px;\n color: #000;\n max-width: 100%;\n box-sizing: border-box;\n padding: .5rem;\n margin: 1em;\n overflow-wrap: break-word\n }\n </style>\n</head>\n<body>\n SSO login complete. You may now close this tab and return to the command prompt.\n</body>\n</html>`;\n\nexport async function getSessionUsingBrowserAuthFlowAsync({\n expoWebsiteUrl,\n}: {\n expoWebsiteUrl: string;\n}): Promise<string> {\n const scheme = 'http';\n const hostname = 'localhost';\n const path = '/auth/callback';\n\n const buildExpoSsoLoginUrl = (port: number): string => {\n const data = {\n app_redirect_uri: `${scheme}://${hostname}:${port}${path}`,\n };\n const params = querystring.stringify(data);\n return `${expoWebsiteUrl}/sso-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 try {\n if (!(request.method === 'GET' && request.url?.includes(path))) {\n throw new Error('Unexpected SSO 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 response.writeHead(200, { 'Content-Type': 'text/html' });\n response.write(successBody);\n response.end();\n } catch (error) {\n reject(error);\n } finally {\n server.close();\n // Ensure that the server shuts down\n for (const connection of connections) {\n connection.destroy();\n }\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 = buildExpoSsoLoginUrl(port);\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","successBody","expoWebsiteUrl","scheme","hostname","path","buildExpoSsoLoginUrl","port","data","app_redirect_uri","params","querystring","stringify","executeAuthFlow","Promise","resolve","reject","connections","Set","server","http","createServer","request","response","method","url","includes","Error","URL","headers","host","sessionSecret","searchParams","get","writeHead","write","end","error","close","connection","destroy","listen","Log","log","address","assert","authorizeUrl","openBrowserAsync","on","add","delete"],"mappings":";;;;+BAsCsBA;;;eAAAA;;;;gEAtCH;;;;;;;gEACU;;;;;;;gEACZ;;;;;;;gEAEO;;;;;;6DAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAErB,MAAMC,cAAc,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4Bd,CAAC;AAED,eAAeD,oCAAoC,EACxDE,cAAc,EAGf;IACC,MAAMC,SAAS;IACf,MAAMC,WAAW;IACjB,MAAMC,OAAO;IAEb,MAAMC,uBAAuB,CAACC;QAC5B,MAAMC,OAAO;YACXC,kBAAkB,GAAGN,OAAO,GAAG,EAAEC,SAAS,CAAC,EAAEG,OAAOF,MAAM;QAC5D;QACA,MAAMK,SAASC,sBAAW,CAACC,SAAS,CAACJ;QACrC,OAAO,GAAGN,eAAe,WAAW,EAAEQ,QAAQ;IAChD;IAEA,mCAAmC;IACnC,MAAMG,kBAAkB;QACtB,OAAO,IAAIC,QAAgB,OAAOC,SAASC;YACzC,MAAMC,cAAc,IAAIC;YAExB,MAAMC,SAASC,eAAI,CAACC,YAAY,CAC9B,CAACC,SAA+BC;gBAC9B,IAAI;wBACgCD;oBAAlC,IAAI,CAAEA,CAAAA,QAAQE,MAAM,KAAK,WAASF,eAAAA,QAAQG,GAAG,qBAAXH,aAAaI,QAAQ,CAACrB,MAAI,GAAI;wBAC9D,MAAM,IAAIsB,MAAM;oBAClB;oBACA,MAAMF,MAAM,IAAIG,IAAIN,QAAQG,GAAG,EAAE,CAAC,KAAK,EAAEH,QAAQO,OAAO,CAACC,IAAI,EAAE;oBAC/D,MAAMC,gBAAgBN,IAAIO,YAAY,CAACC,GAAG,CAAC;oBAE3C,IAAI,CAACF,eAAe;wBAClB,MAAM,IAAIJ,MAAM;oBAClB;oBACAZ,QAAQgB;oBACRR,SAASW,SAAS,CAAC,KAAK;wBAAE,gBAAgB;oBAAY;oBACtDX,SAASY,KAAK,CAAClC;oBACfsB,SAASa,GAAG;gBACd,EAAE,OAAOC,OAAO;oBACdrB,OAAOqB;gBACT,SAAU;oBACRlB,OAAOmB,KAAK;oBACZ,oCAAoC;oBACpC,KAAK,MAAMC,cAActB,YAAa;wBACpCsB,WAAWC,OAAO;oBACpB;gBACF;YACF;YAGFrB,OAAOsB,MAAM,CAAC,GAAGrC,UAAU;gBACzBsC,KAAIC,GAAG,CAAC;gBAER,MAAMC,UAAUzB,OAAOyB,OAAO;gBAC9BC,IAAAA,iBAAM,EACJD,YAAY,QAAQ,OAAOA,YAAY,UACvC;gBAEF,MAAMrC,OAAOqC,QAAQrC,IAAI;gBACzB,MAAMuC,eAAexC,qBAAqBC;gBAC1CwC,IAAAA,oBAAgB,EAACD;YACnB;YAEA3B,OAAO6B,EAAE,CAAC,cAAc,CAACT;gBACvBtB,YAAYgC,GAAG,CAACV;gBAEhBA,WAAWS,EAAE,CAAC,SAAS;oBACrB/B,YAAYiC,MAAM,CAACX;gBACrB;YACF;QACF;IACF;IAEA,OAAO,MAAM1B;AACf"}
1
+ {"version":3,"sources":["../../../../src/api/user/expoSsoLauncher.ts"],"sourcesContent":["import assert from 'assert';\nimport openBrowserAsync from 'better-opn';\nimport http from 'http';\nimport { 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"}
@@ -12,6 +12,9 @@ _export(exports, {
12
12
  ANONYMOUS_USERNAME: function() {
13
13
  return ANONYMOUS_USERNAME;
14
14
  },
15
+ browserLoginAsync: function() {
16
+ return browserLoginAsync;
17
+ },
15
18
  getActorDisplayName: function() {
16
19
  return getActorDisplayName;
17
20
  },
@@ -23,9 +26,6 @@ _export(exports, {
23
26
  },
24
27
  logoutAsync: function() {
25
28
  return logoutAsync;
26
- },
27
- ssoLoginAsync: function() {
28
- return ssoLoginAsync;
29
29
  }
30
30
  });
31
31
  function _fs() {
@@ -124,9 +124,10 @@ async function loginAsync(credentials) {
124
124
  currentConnection: 'Username-Password-Authentication'
125
125
  });
126
126
  }
127
- async function ssoLoginAsync() {
127
+ async function browserLoginAsync({ sso = false }) {
128
128
  const sessionSecret = await (0, _expoSsoLauncher.getSessionUsingBrowserAuthFlowAsync)({
129
- expoWebsiteUrl: (0, _endpoint.getExpoWebsiteBaseUrl)()
129
+ expoWebsiteUrl: (0, _endpoint.getExpoWebsiteBaseUrl)(),
130
+ sso
130
131
  });
131
132
  const userData = await _UserQuery.UserQuery.meUserActorAsync({
132
133
  'expo-session': sessionSecret
@@ -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 ssoLoginAsync(): Promise<void> {\n const sessionSecret = await getSessionUsingBrowserAuthFlowAsync({\n expoWebsiteUrl: getExpoWebsiteBaseUrl(),\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","getActorDisplayName","getUserAsync","loginAsync","logoutAsync","ssoLoginAsync","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","getSessionUsingBrowserAuthFlowAsync","expoWebsiteUrl","getExpoWebsiteBaseUrl","Promise","all","fs","rm","getDevelopmentCodeSigningDirectory","recursive","force","Log","log"],"mappings":";;;;;;;;;;;IAaaA,kBAAkB;eAAlBA;;IAOGC,mBAAmB;eAAnBA;;IAeMC,YAAY;eAAZA;;IASAC,UAAU;eAAVA;;IAuCAC,WAAW;eAAXA;;IAfAC,aAAa;eAAbA;;;;yBApES;;;;;;8BAE6B;iCACR;6DAC/B;6BAC8B;qBAC/B;0BACkB;2BACA;wBACX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAE3B,IAAIC;AAEG,MAAMN,qBAAqB;AAO3B,SAASC,oBAAoBM,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,eAAeE;QACuBS;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,eAAeH,WAAWiB,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,eAAe7B;IACpB,MAAMS,gBAAgB,MAAMqB,IAAAA,oDAAmC,EAAC;QAC9DC,gBAAgBC,IAAAA,+BAAqB;IACvC;IACA,MAAMR,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,eAAe9B;IACpBE,cAAca;IACd,MAAMmB,QAAQC,GAAG,CAAC;QAChBC,cAAE,CAACC,EAAE,CAACC,IAAAA,+CAAkC,KAAI;YAAEC,WAAW;YAAMC,OAAO;QAAK;QAC3Eb,IAAAA,6BAAe,EAACZ;KACjB;IACD0B,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\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":";;;;;;;;;;;IAaaA,kBAAkB;eAAlBA;;IAuDSC,iBAAiB;eAAjBA;;IAhDNC,mBAAmB;eAAnBA;;IAeMC,YAAY;eAAZA;;IASAC,UAAU;eAAVA;;IAwCAC,WAAW;eAAXA;;;;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"}
@@ -60,11 +60,13 @@ const expoLogin = async (argv)=>{
60
60
  '--password': String,
61
61
  '--otp': String,
62
62
  '--sso': Boolean,
63
+ '--browser': Boolean,
63
64
  // Aliases
64
65
  '-h': '--help',
65
66
  '-u': '--username',
66
67
  '-p': '--password',
67
- '-s': '--sso'
68
+ '-s': '--sso',
69
+ '-b': '--browser'
68
70
  }, argv);
69
71
  if (args['--help']) {
70
72
  (0, _args.printHelp)(`Log in to an Expo account`, `npx expo login`, [
@@ -72,6 +74,7 @@ const expoLogin = async (argv)=>{
72
74
  `-p, --password <string> Password`,
73
75
  `--otp <string> One-time password from your 2FA device`,
74
76
  `-s, --sso Log in with SSO`,
77
+ `-b, --browser Log in with a browser`,
75
78
  `-h, --help Usage info`
76
79
  ].join('\n'));
77
80
  }
@@ -81,7 +84,8 @@ const expoLogin = async (argv)=>{
81
84
  username: args['--username'],
82
85
  password: args['--password'],
83
86
  otp: args['--otp'],
84
- sso: !!args['--sso']
87
+ sso: !!args['--sso'],
88
+ browser: !!args['--browser']
85
89
  }).catch(_errors.logCmdError);
86
90
  };
87
91
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/login/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from '../../bin/cli';\nimport { assertArgs, printHelp } from '../utils/args';\nimport { logCmdError } from '../utils/errors';\n\nexport const expoLogin: Command = async (argv) => {\n const args = assertArgs(\n {\n // Types\n '--help': Boolean,\n '--username': String,\n '--password': String,\n '--otp': String,\n '--sso': Boolean,\n // Aliases\n '-h': '--help',\n '-u': '--username',\n '-p': '--password',\n '-s': '--sso',\n },\n argv\n );\n\n if (args['--help']) {\n printHelp(\n `Log in to an Expo account`,\n `npx expo login`,\n [\n `-u, --username <string> Username`,\n `-p, --password <string> Password`,\n `--otp <string> One-time password from your 2FA device`,\n `-s, --sso Log in with SSO`,\n `-h, --help Usage info`,\n ].join('\\n')\n );\n }\n\n const { showLoginPromptAsync } = await import('../api/user/actions.js');\n return showLoginPromptAsync({\n // Parsed options\n username: args['--username'],\n password: args['--password'],\n otp: args['--otp'],\n sso: !!args['--sso'],\n }).catch(logCmdError);\n};\n"],"names":["expoLogin","argv","args","assertArgs","Boolean","String","printHelp","join","showLoginPromptAsync","username","password","otp","sso","catch","logCmdError"],"mappings":";;;;;+BAKaA;;;eAAAA;;;sBAHyB;wBACV;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAErB,MAAMA,YAAqB,OAAOC;IACvC,MAAMC,OAAOC,IAAAA,gBAAU,EACrB;QACE,QAAQ;QACR,UAAUC;QACV,cAAcC;QACd,cAAcA;QACd,SAASA;QACT,SAASD;QACT,UAAU;QACV,MAAM;QACN,MAAM;QACN,MAAM;QACN,MAAM;IACR,GACAH;IAGF,IAAIC,IAAI,CAAC,SAAS,EAAE;QAClBI,IAAAA,eAAS,EACP,CAAC,yBAAyB,CAAC,EAC3B,CAAC,cAAc,CAAC,EAChB;YACE,CAAC,iCAAiC,CAAC;YACnC,CAAC,iCAAiC,CAAC;YACnC,CAAC,+DAA+D,CAAC;YACjE,CAAC,wCAAwC,CAAC;YAC1C,CAAC,mCAAmC,CAAC;SACtC,CAACC,IAAI,CAAC;IAEX;IAEA,MAAM,EAAEC,oBAAoB,EAAE,GAAG,MAAM,mEAAA,QAAO;IAC9C,OAAOA,qBAAqB;QAC1B,iBAAiB;QACjBC,UAAUP,IAAI,CAAC,aAAa;QAC5BQ,UAAUR,IAAI,CAAC,aAAa;QAC5BS,KAAKT,IAAI,CAAC,QAAQ;QAClBU,KAAK,CAAC,CAACV,IAAI,CAAC,QAAQ;IACtB,GAAGW,KAAK,CAACC,mBAAW;AACtB"}
1
+ {"version":3,"sources":["../../../src/login/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from '../../bin/cli';\nimport { assertArgs, printHelp } from '../utils/args';\nimport { logCmdError } from '../utils/errors';\n\nexport const expoLogin: Command = async (argv) => {\n const args = assertArgs(\n {\n // Types\n '--help': Boolean,\n '--username': String,\n '--password': String,\n '--otp': String,\n '--sso': Boolean,\n '--browser': Boolean,\n // Aliases\n '-h': '--help',\n '-u': '--username',\n '-p': '--password',\n '-s': '--sso',\n '-b': '--browser',\n },\n argv\n );\n\n if (args['--help']) {\n printHelp(\n `Log in to an Expo account`,\n `npx expo login`,\n [\n `-u, --username <string> Username`,\n `-p, --password <string> Password`,\n `--otp <string> One-time password from your 2FA device`,\n `-s, --sso Log in with SSO`,\n `-b, --browser Log in with a browser`,\n `-h, --help Usage info`,\n ].join('\\n')\n );\n }\n\n const { showLoginPromptAsync } = await import('../api/user/actions.js');\n return showLoginPromptAsync({\n // Parsed options\n username: args['--username'],\n password: args['--password'],\n otp: args['--otp'],\n sso: !!args['--sso'],\n browser: !!args['--browser'],\n }).catch(logCmdError);\n};\n"],"names":["expoLogin","argv","args","assertArgs","Boolean","String","printHelp","join","showLoginPromptAsync","username","password","otp","sso","browser","catch","logCmdError"],"mappings":";;;;;+BAKaA;;;eAAAA;;;sBAHyB;wBACV;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAErB,MAAMA,YAAqB,OAAOC;IACvC,MAAMC,OAAOC,IAAAA,gBAAU,EACrB;QACE,QAAQ;QACR,UAAUC;QACV,cAAcC;QACd,cAAcA;QACd,SAASA;QACT,SAASD;QACT,aAAaA;QACb,UAAU;QACV,MAAM;QACN,MAAM;QACN,MAAM;QACN,MAAM;QACN,MAAM;IACR,GACAH;IAGF,IAAIC,IAAI,CAAC,SAAS,EAAE;QAClBI,IAAAA,eAAS,EACP,CAAC,yBAAyB,CAAC,EAC3B,CAAC,cAAc,CAAC,EAChB;YACE,CAAC,iCAAiC,CAAC;YACnC,CAAC,iCAAiC,CAAC;YACnC,CAAC,+DAA+D,CAAC;YACjE,CAAC,wCAAwC,CAAC;YAC1C,CAAC,8CAA8C,CAAC;YAChD,CAAC,mCAAmC,CAAC;SACtC,CAACC,IAAI,CAAC;IAEX;IAEA,MAAM,EAAEC,oBAAoB,EAAE,GAAG,MAAM,mEAAA,QAAO;IAC9C,OAAOA,qBAAqB;QAC1B,iBAAiB;QACjBC,UAAUP,IAAI,CAAC,aAAa;QAC5BQ,UAAUR,IAAI,CAAC,aAAa;QAC5BS,KAAKT,IAAI,CAAC,QAAQ;QAClBU,KAAK,CAAC,CAACV,IAAI,CAAC,QAAQ;QACpBW,SAAS,CAAC,CAACX,IAAI,CAAC,YAAY;IAC9B,GAAGY,KAAK,CAACC,mBAAW;AACtB"}
@@ -47,9 +47,13 @@ function _interop_require_default(obj) {
47
47
  * @see https://developer.android.com/studio/intro/studio-config#optimize-studio-windows
48
48
  */ const ANDROID_DEFAULT_LOCATION = {
49
49
  darwin: _path().default.join(_os().default.homedir(), 'Library', 'Android', 'sdk'),
50
- linux: _path().default.join(_os().default.homedir(), 'Android', 'sdk'),
50
+ linux: [
51
+ _path().default.join(_os().default.homedir(), 'Android', 'Sdk'),
52
+ _path().default.join(_os().default.homedir(), 'Android', 'sdk')
53
+ ],
51
54
  win32: _path().default.join(_os().default.homedir(), 'AppData', 'Local', 'Android', 'Sdk')
52
55
  };
56
+ const isAndroidDefaultLocationKey = (platform)=>ANDROID_DEFAULT_LOCATION[platform] != null;
53
57
  function assertSdkRoot() {
54
58
  if (process.env.ANDROID_HOME) {
55
59
  (0, _assert().default)(_fs().default.existsSync(process.env.ANDROID_HOME), `Failed to resolve the Android SDK path. ANDROID_HOME is set to a non-existing path: ${process.env.ANDROID_HOME}`);
@@ -59,12 +63,17 @@ function assertSdkRoot() {
59
63
  (0, _assert().default)(_fs().default.existsSync(process.env.ANDROID_SDK_ROOT), `Failed to resolve the Android SDK path. Deprecated ANDROID_SDK_ROOT is set to a non-existing path: ${process.env.ANDROID_SDK_ROOT}. Use ANDROID_HOME instead.`);
60
64
  return process.env.ANDROID_SDK_ROOT;
61
65
  }
62
- const defaultLocation = ANDROID_DEFAULT_LOCATION[process.platform];
63
- if (defaultLocation) {
64
- (0, _assert().default)(_fs().default.existsSync(defaultLocation), `Failed to resolve the Android SDK path. Default install location not found: ${defaultLocation}. Use ANDROID_HOME to set the Android SDK location.`);
65
- return defaultLocation;
66
+ const platform = process.platform;
67
+ if (!isAndroidDefaultLocationKey(platform)) {
68
+ return null;
66
69
  }
67
- return null;
70
+ const defaultLocation = ANDROID_DEFAULT_LOCATION[platform];
71
+ const locations = !Array.isArray(defaultLocation) ? [
72
+ defaultLocation
73
+ ] : defaultLocation;
74
+ const resolvedLocation = locations.find((location)=>_fs().default.existsSync(location));
75
+ (0, _assert().default)(!!resolvedLocation, `Failed to resolve the Android SDK path. Default install location not found: ${locations[0]}. Use ANDROID_HOME to set the Android SDK location.`);
76
+ return resolvedLocation;
68
77
  }
69
78
 
70
79
  //# sourceMappingURL=AndroidSdk.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/start/platforms/android/AndroidSdk.ts"],"sourcesContent":["import assert from 'assert';\nimport fs from 'fs';\nimport os from 'os';\nimport path from 'path';\n\n/**\n * The default Android SDK locations per platform.\n * @see https://developer.android.com/studio/run/emulator-commandline#filedir\n * @see https://developer.android.com/studio/intro/studio-config#optimize-studio-windows\n */\nconst ANDROID_DEFAULT_LOCATION: Readonly<Partial<Record<NodeJS.Platform, string>>> = {\n darwin: path.join(os.homedir(), 'Library', 'Android', 'sdk'),\n linux: path.join(os.homedir(), 'Android', 'sdk'),\n win32: path.join(os.homedir(), 'AppData', 'Local', 'Android', 'Sdk'),\n};\n\n/**\n * Resolve and validate the root folder where the Android SDK has been installed.\n * This checks both `ANDROID_HOME`, `ANDROID_SDK_ROOT`, and the default path for the current platform.\n * @see https://developer.android.com/studio/command-line/variables\n */\nexport function assertSdkRoot() {\n if (process.env.ANDROID_HOME) {\n assert(\n fs.existsSync(process.env.ANDROID_HOME),\n `Failed to resolve the Android SDK path. ANDROID_HOME is set to a non-existing path: ${process.env.ANDROID_HOME}`\n );\n return process.env.ANDROID_HOME;\n }\n\n if (process.env.ANDROID_SDK_ROOT) {\n assert(\n fs.existsSync(process.env.ANDROID_SDK_ROOT),\n `Failed to resolve the Android SDK path. Deprecated ANDROID_SDK_ROOT is set to a non-existing path: ${process.env.ANDROID_SDK_ROOT}. Use ANDROID_HOME instead.`\n );\n return process.env.ANDROID_SDK_ROOT;\n }\n\n const defaultLocation = ANDROID_DEFAULT_LOCATION[process.platform];\n if (defaultLocation) {\n assert(\n fs.existsSync(defaultLocation),\n `Failed to resolve the Android SDK path. Default install location not found: ${defaultLocation}. Use ANDROID_HOME to set the Android SDK location.`\n );\n return defaultLocation;\n }\n\n return null;\n}\n"],"names":["assertSdkRoot","ANDROID_DEFAULT_LOCATION","darwin","path","join","os","homedir","linux","win32","process","env","ANDROID_HOME","assert","fs","existsSync","ANDROID_SDK_ROOT","defaultLocation","platform"],"mappings":";;;;+BAqBgBA;;;eAAAA;;;;gEArBG;;;;;;;gEACJ;;;;;;;gEACA;;;;;;;gEACE;;;;;;;;;;;AAEjB;;;;CAIC,GACD,MAAMC,2BAA+E;IACnFC,QAAQC,eAAI,CAACC,IAAI,CAACC,aAAE,CAACC,OAAO,IAAI,WAAW,WAAW;IACtDC,OAAOJ,eAAI,CAACC,IAAI,CAACC,aAAE,CAACC,OAAO,IAAI,WAAW;IAC1CE,OAAOL,eAAI,CAACC,IAAI,CAACC,aAAE,CAACC,OAAO,IAAI,WAAW,SAAS,WAAW;AAChE;AAOO,SAASN;IACd,IAAIS,QAAQC,GAAG,CAACC,YAAY,EAAE;QAC5BC,IAAAA,iBAAM,EACJC,aAAE,CAACC,UAAU,CAACL,QAAQC,GAAG,CAACC,YAAY,GACtC,CAAC,oFAAoF,EAAEF,QAAQC,GAAG,CAACC,YAAY,EAAE;QAEnH,OAAOF,QAAQC,GAAG,CAACC,YAAY;IACjC;IAEA,IAAIF,QAAQC,GAAG,CAACK,gBAAgB,EAAE;QAChCH,IAAAA,iBAAM,EACJC,aAAE,CAACC,UAAU,CAACL,QAAQC,GAAG,CAACK,gBAAgB,GAC1C,CAAC,mGAAmG,EAAEN,QAAQC,GAAG,CAACK,gBAAgB,CAAC,2BAA2B,CAAC;QAEjK,OAAON,QAAQC,GAAG,CAACK,gBAAgB;IACrC;IAEA,MAAMC,kBAAkBf,wBAAwB,CAACQ,QAAQQ,QAAQ,CAAC;IAClE,IAAID,iBAAiB;QACnBJ,IAAAA,iBAAM,EACJC,aAAE,CAACC,UAAU,CAACE,kBACd,CAAC,4EAA4E,EAAEA,gBAAgB,mDAAmD,CAAC;QAErJ,OAAOA;IACT;IAEA,OAAO;AACT"}
1
+ {"version":3,"sources":["../../../../../src/start/platforms/android/AndroidSdk.ts"],"sourcesContent":["import assert from 'assert';\nimport fs from 'fs';\nimport os from 'os';\nimport path from 'path';\n\n/**\n * The default Android SDK locations per platform.\n * @see https://developer.android.com/studio/run/emulator-commandline#filedir\n * @see https://developer.android.com/studio/intro/studio-config#optimize-studio-windows\n */\nconst ANDROID_DEFAULT_LOCATION = {\n darwin: path.join(os.homedir(), 'Library', 'Android', 'sdk'),\n linux: [path.join(os.homedir(), 'Android', 'Sdk'), path.join(os.homedir(), 'Android', 'sdk')],\n win32: path.join(os.homedir(), 'AppData', 'Local', 'Android', 'Sdk'),\n};\n\nconst isAndroidDefaultLocationKey = (\n platform: string\n): platform is keyof typeof ANDROID_DEFAULT_LOCATION =>\n ANDROID_DEFAULT_LOCATION[platform as keyof typeof ANDROID_DEFAULT_LOCATION] != null;\n\n/**\n * Resolve and validate the root folder where the Android SDK has been installed.\n * This checks both `ANDROID_HOME`, `ANDROID_SDK_ROOT`, and the default path for the current platform.\n * @see https://developer.android.com/studio/command-line/variables\n */\nexport function assertSdkRoot(): string | null {\n if (process.env.ANDROID_HOME) {\n assert(\n fs.existsSync(process.env.ANDROID_HOME),\n `Failed to resolve the Android SDK path. ANDROID_HOME is set to a non-existing path: ${process.env.ANDROID_HOME}`\n );\n return process.env.ANDROID_HOME;\n }\n\n if (process.env.ANDROID_SDK_ROOT) {\n assert(\n fs.existsSync(process.env.ANDROID_SDK_ROOT),\n `Failed to resolve the Android SDK path. Deprecated ANDROID_SDK_ROOT is set to a non-existing path: ${process.env.ANDROID_SDK_ROOT}. Use ANDROID_HOME instead.`\n );\n return process.env.ANDROID_SDK_ROOT;\n }\n\n const platform = process.platform;\n if (!isAndroidDefaultLocationKey(platform)) {\n return null;\n }\n\n const defaultLocation = ANDROID_DEFAULT_LOCATION[platform];\n const locations = !Array.isArray(defaultLocation) ? [defaultLocation] : defaultLocation;\n const resolvedLocation = locations.find((location) => fs.existsSync(location));\n assert(\n !!resolvedLocation,\n `Failed to resolve the Android SDK path. Default install location not found: ${locations[0]}. Use ANDROID_HOME to set the Android SDK location.`\n );\n return resolvedLocation;\n}\n"],"names":["assertSdkRoot","ANDROID_DEFAULT_LOCATION","darwin","path","join","os","homedir","linux","win32","isAndroidDefaultLocationKey","platform","process","env","ANDROID_HOME","assert","fs","existsSync","ANDROID_SDK_ROOT","defaultLocation","locations","Array","isArray","resolvedLocation","find","location"],"mappings":";;;;+BA0BgBA;;;eAAAA;;;;gEA1BG;;;;;;;gEACJ;;;;;;;gEACA;;;;;;;gEACE;;;;;;;;;;;AAEjB;;;;CAIC,GACD,MAAMC,2BAA2B;IAC/BC,QAAQC,eAAI,CAACC,IAAI,CAACC,aAAE,CAACC,OAAO,IAAI,WAAW,WAAW;IACtDC,OAAO;QAACJ,eAAI,CAACC,IAAI,CAACC,aAAE,CAACC,OAAO,IAAI,WAAW;QAAQH,eAAI,CAACC,IAAI,CAACC,aAAE,CAACC,OAAO,IAAI,WAAW;KAAO;IAC7FE,OAAOL,eAAI,CAACC,IAAI,CAACC,aAAE,CAACC,OAAO,IAAI,WAAW,SAAS,WAAW;AAChE;AAEA,MAAMG,8BAA8B,CAClCC,WAEAT,wBAAwB,CAACS,SAAkD,IAAI;AAO1E,SAASV;IACd,IAAIW,QAAQC,GAAG,CAACC,YAAY,EAAE;QAC5BC,IAAAA,iBAAM,EACJC,aAAE,CAACC,UAAU,CAACL,QAAQC,GAAG,CAACC,YAAY,GACtC,CAAC,oFAAoF,EAAEF,QAAQC,GAAG,CAACC,YAAY,EAAE;QAEnH,OAAOF,QAAQC,GAAG,CAACC,YAAY;IACjC;IAEA,IAAIF,QAAQC,GAAG,CAACK,gBAAgB,EAAE;QAChCH,IAAAA,iBAAM,EACJC,aAAE,CAACC,UAAU,CAACL,QAAQC,GAAG,CAACK,gBAAgB,GAC1C,CAAC,mGAAmG,EAAEN,QAAQC,GAAG,CAACK,gBAAgB,CAAC,2BAA2B,CAAC;QAEjK,OAAON,QAAQC,GAAG,CAACK,gBAAgB;IACrC;IAEA,MAAMP,WAAWC,QAAQD,QAAQ;IACjC,IAAI,CAACD,4BAA4BC,WAAW;QAC1C,OAAO;IACT;IAEA,MAAMQ,kBAAkBjB,wBAAwB,CAACS,SAAS;IAC1D,MAAMS,YAAY,CAACC,MAAMC,OAAO,CAACH,mBAAmB;QAACA;KAAgB,GAAGA;IACxE,MAAMI,mBAAmBH,UAAUI,IAAI,CAAC,CAACC,WAAaT,aAAE,CAACC,UAAU,CAACQ;IACpEV,IAAAA,iBAAM,EACJ,CAAC,CAACQ,kBACF,CAAC,4EAA4E,EAAEH,SAAS,CAAC,EAAE,CAAC,mDAAmD,CAAC;IAElJ,OAAOG;AACT"}
@@ -183,7 +183,10 @@ async function getConnectedAppleDevicesAsync() {
183
183
  ]);
184
184
  debug(devices.stdout);
185
185
  const devicesJson = await _jsonfile().default.readAsync(tmpPath);
186
- if ((devicesJson == null ? void 0 : (_devicesJson_info = devicesJson.info) == null ? void 0 : _devicesJson_info.jsonVersion) !== 2) {
186
+ if (![
187
+ 2,
188
+ 3
189
+ ].includes(devicesJson == null ? void 0 : (_devicesJson_info = devicesJson.info) == null ? void 0 : _devicesJson_info.jsonVersion)) {
187
190
  _log.warn('Unexpected devicectl JSON version output from devicectl. Connecting to physical Apple devices may not work as expected.');
188
191
  }
189
192
  assertDevicesJson(devicesJson);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/start/platforms/ios/devicectl.ts"],"sourcesContent":["/**\n * Copyright © 2024 650 Industries.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nimport JsonFile from '@expo/json-file';\nimport spawnAsync, { SpawnOptions, SpawnResult } from '@expo/spawn-async';\nimport chalk from 'chalk';\nimport { spawn, execSync } from 'child_process';\nimport fs from 'fs';\nimport assert from 'node:assert';\nimport { Ora } from 'ora';\nimport { EOL } from 'os';\nimport path from 'path';\n\nimport { xcrunAsync } from './xcrun';\nimport { getExpoHomeDirectory } from '../../../api/user/UserSettings';\nimport * as Log from '../../../log';\nimport { createTempFilePath } from '../../../utils/createTempPath';\nimport { CommandError } from '../../../utils/errors';\nimport { installExitHooks } from '../../../utils/exit';\nimport { isInteractive } from '../../../utils/interactive';\nimport { ora } from '../../../utils/ora';\nimport { confirmAsync } from '../../../utils/prompts';\n\nconst DEVICE_CTL_EXISTS_PATH = path.join(getExpoHomeDirectory(), 'devicectl-exists');\n\nconst debug = require('debug')('expo:devicectl') as typeof console.log;\n\ntype AnyEnum<T extends string = string> = T | (string & object);\n\ntype DeviceCtlDevice = {\n capabilities: DeviceCtlDeviceCapability[];\n connectionProperties: DeviceCtlConnectionProperties;\n deviceProperties: DeviceCtlDeviceProperties;\n hardwareProperties: DeviceCtlHardwareProperties;\n /** \"A1A1AAA1-0011-1AA1-11A1-10A1111AA11A\" */\n identifier: string;\n visibilityClass: AnyEnum<'default'>;\n};\n\ntype DeviceCtlHardwareProperties = {\n cpuType: DeviceCtlCpuType;\n deviceType: AnyEnum<'iPhone'>;\n /** 1114404411111111 */\n ecid: number;\n /** \"D74AP\" */\n hardwareModel: string;\n /** 512000000000 */\n internalStorageCapacity: number;\n /** true */\n isProductionFused: boolean;\n /** \"iPhone 14 Pro Max\" */\n marketingName: string;\n /** \"iOS\" */\n platform: AnyEnum<'iOS' | 'xrOS'>;\n /** \"iPhone15,3\" */\n productType: AnyEnum<'iPhone13,4' | 'iPhone15,3'>;\n reality: AnyEnum<'physical'>;\n /** \"X2X1CC1XXX\" */\n serialNumber: string;\n supportedCPUTypes: DeviceCtlCpuType[];\n /** [1] */\n supportedDeviceFamilies: number[];\n thinningProductType: AnyEnum<'iPhone15,3'>;\n /** \"00001110-001111110110101A\" */\n udid: string;\n};\n\ntype DeviceCtlDeviceProperties = {\n /** true */\n bootedFromSnapshot: boolean;\n /** \"com.apple.os.update-AD0CF111ACFF11A11111A76A3D1262AE42A3F56F305AF5AE1135393A7A14A7D1\" */\n bootedSnapshotName: string;\n /** false */\n ddiServicesAvailable: boolean;\n\n developerModeStatus: AnyEnum<'enabled'>;\n /** false */\n hasInternalOSBuild: boolean;\n /** \"Evan's phone\" */\n name: string;\n /** \"21E236\" */\n osBuildUpdate: string;\n /** \"17.4.1\" */\n osVersionNumber: string;\n /** false */\n rootFileSystemIsWritable: boolean;\n};\n\ntype DeviceCtlDeviceCapability =\n | {\n name: AnyEnum;\n featureIdentifier: AnyEnum;\n }\n | {\n featureIdentifier: 'com.apple.coredevice.feature.connectdevice';\n name: 'Connect to Device';\n }\n | {\n featureIdentifier: 'com.apple.coredevice.feature.unpairdevice';\n name: 'Unpair Device';\n }\n | {\n featureIdentifier: 'com.apple.coredevice.feature.acquireusageassertion';\n name: 'Acquire Usage Assertion';\n };\n\ntype DeviceCtlConnectionProperties = {\n authenticationType: AnyEnum<'manualPairing'>;\n isMobileDeviceOnly: boolean;\n /** \"2024-04-20T22:50:04.244Z\" */\n lastConnectionDate: string;\n pairingState: AnyEnum<'paired'>;\n /** [\"00001111-001111110110101A.coredevice.local\", \"A1A1AAA1-0011-1AA1-11A1-10A1111AA11A.coredevice.local\"] */\n potentialHostnames: string[];\n transportType: AnyEnum<'localNetwork' | 'wired'>;\n tunnelState: AnyEnum<'disconnected' | 'unavailable'>;\n tunnelTransportProtocol: AnyEnum<'tcp'>;\n};\n\ntype DeviceCtlCpuType = {\n name: AnyEnum<'arm64e' | 'arm64' | 'arm64_32'>;\n subType: number;\n /** 16777228 */\n type: number;\n};\n\n/** Run a `devicectl` command. */\nexport async function devicectlAsync(\n args: (string | undefined)[],\n options?: SpawnOptions\n): Promise<SpawnResult> {\n try {\n return await xcrunAsync(['devicectl', ...args], options);\n } catch (error: any) {\n if (error instanceof CommandError) {\n throw error;\n }\n if ('stderr' in error) {\n const errorCodes = getDeviceCtlErrorCodes(error.stderr);\n if (errorCodes.includes('Locked')) {\n throw new CommandError('APPLE_DEVICE_LOCKED', 'Device is locked, unlock and try again.');\n }\n }\n throw error;\n }\n}\n\nexport async function getConnectedAppleDevicesAsync() {\n if (!hasDevicectlEverBeenInstalled()) {\n debug('devicectl not found, skipping remote Apple devices.');\n return [];\n }\n\n const tmpPath = createTempFilePath();\n const devices = await devicectlAsync([\n 'list',\n 'devices',\n '--json-output',\n tmpPath,\n // Give two seconds before timing out: between 5 and 9223372036854775807\n '--timeout',\n '5',\n ]);\n debug(devices.stdout);\n const devicesJson = await JsonFile.readAsync(tmpPath);\n\n if ((devicesJson as any)?.info?.jsonVersion !== 2) {\n Log.warn(\n 'Unexpected devicectl JSON version output from devicectl. Connecting to physical Apple devices may not work as expected.'\n );\n }\n\n assertDevicesJson(devicesJson);\n\n return devicesJson.result.devices as DeviceCtlDevice[];\n}\n\nfunction assertDevicesJson(\n results: any\n): asserts results is { result: { devices: DeviceCtlDevice[] } } {\n assert(\n results != null && 'result' in results && Array.isArray(results?.result?.devices),\n 'Malformed JSON output from devicectl: ' + JSON.stringify(results, null, 2)\n );\n}\n\nexport async function launchBinaryOnMacAsync(\n bundleId: string,\n appBinaryPath: string\n): Promise<void> {\n const args = ['-b', bundleId, appBinaryPath];\n try {\n await spawnAsync('open', args);\n } catch (error: any) {\n if ('code' in error) {\n if (error.code === 1) {\n throw new CommandError(\n 'MACOS_LAUNCH',\n 'Failed to launch the compatible binary on macOS: open ' +\n args.join(' ') +\n '\\n\\n' +\n error.message\n );\n }\n }\n throw error;\n }\n}\n\nasync function installAppWithDeviceCtlAsync(\n uuid: string,\n bundleIdOrAppPath: string,\n onProgress: (event: { status: string; isComplete: boolean; progress: number }) => void\n): Promise<void> {\n // 𝝠 xcrun devicectl device install app --device 00001110-001111110110101A /Users/evanbacon/Library/Developer/Xcode/DerivedData/Router-hgbqaxzhrhkiftfweydvhgttadvn/Build/Products/Debug-iphoneos/Router.app --verbose\n return new Promise((resolve, reject) => {\n const args: string[] = [\n 'devicectl',\n 'device',\n 'install',\n 'app',\n '--device',\n uuid,\n bundleIdOrAppPath,\n ];\n const childProcess = spawn('xcrun', args);\n debug('xcrun ' + args.join(' '));\n\n let currentProgress = 0;\n let hasStarted = false;\n\n function updateProgress(progress: number) {\n hasStarted = true;\n if (progress <= currentProgress) {\n return;\n }\n currentProgress = progress;\n onProgress({\n progress,\n isComplete: progress === 100,\n status: 'Installing',\n });\n }\n\n childProcess.stdout.on('data', (data: Buffer) => {\n // Sometimes more than one chunk comes at a time, here we split by system newline,\n // then trim and filter.\n const strings = data\n .toString()\n .split(EOL)\n .map((value) => value.trim());\n\n strings.forEach((str) => {\n // Match the progress percentage:\n // - '34%... 35%...' -> 34\n // - '31%...' -> 31\n // - 'Complete!' -> 100\n\n const match = str.match(/(\\d+)%\\.\\.\\./);\n if (match) {\n updateProgress(parseInt(match[1], 10));\n } else if (hasStarted) {\n updateProgress(100);\n }\n });\n\n debug('[stdout]:', strings);\n });\n\n childProcess.on('close', (code) => {\n debug('[close]: ' + code);\n if (code === 0) {\n resolve();\n } else {\n const stderr = childProcess.stderr.read();\n const err = new Error(stderr);\n (err as any).code = code;\n detach(err);\n }\n });\n\n const detach = async (err?: Error) => {\n off?.();\n if (childProcess) {\n return new Promise<void>((resolve) => {\n childProcess?.on('close', resolve);\n childProcess?.kill();\n // childProcess = null;\n reject(err ?? new CommandError('detached'));\n });\n }\n };\n\n const off = installExitHooks(() => detach());\n });\n}\n\nexport async function launchAppWithDeviceCtl(deviceId: string, bundleId: string) {\n await devicectlAsync(['device', 'process', 'launch', '--device', deviceId, bundleId]);\n}\n\n/** Find all error codes from the output log */\nfunction getDeviceCtlErrorCodes(log: string): string[] {\n return [...log.matchAll(/BSErrorCodeDescription\\s+=\\s+(.*)$/gim)].map(([_line, code]) => code);\n}\n\nlet hasEverBeenInstalled: boolean | undefined;\n\nexport function hasDevicectlEverBeenInstalled() {\n if (hasEverBeenInstalled) return hasEverBeenInstalled;\n // It doesn't appear possible for devicectl to ever be uninstalled. We can just check once and store this result forever\n // to prevent cold boots of devicectl from slowing down all invocations of `expo run ios`\n if (fs.existsSync(DEVICE_CTL_EXISTS_PATH)) {\n hasEverBeenInstalled = true;\n return true;\n }\n\n const isInstalled = isDevicectlInstalled();\n\n if (isInstalled) {\n fs.writeFileSync(DEVICE_CTL_EXISTS_PATH, '1');\n }\n hasEverBeenInstalled = isInstalled;\n return isInstalled;\n}\n\nfunction isDevicectlInstalled() {\n try {\n execSync('xcrun devicectl --version', { stdio: 'ignore' });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Wraps the apple device method for installing and running an app,\n * adds indicator and retry loop for when the device is locked.\n */\nexport async function installAndLaunchAppAsync(props: {\n bundle: string;\n bundleIdentifier: string;\n udid: string;\n deviceName: string;\n}): Promise<void> {\n debug('Running on device:', props);\n const { bundle, bundleIdentifier, udid, deviceName } = props;\n let indicator: Ora | undefined;\n\n try {\n if (!indicator) {\n indicator = ora(`Connecting to: ${props.deviceName}`).start();\n }\n\n await installAppWithDeviceCtlAsync(\n udid,\n bundle,\n ({\n status,\n isComplete,\n progress,\n }: {\n status: string;\n isComplete: boolean;\n progress: number;\n }) => {\n if (!indicator) {\n indicator = ora(status).start();\n }\n indicator.text = `${chalk.bold(status)} ${progress}%`;\n if (isComplete) {\n indicator.succeed();\n }\n }\n );\n } catch (error: any) {\n if (indicator) {\n indicator.fail();\n }\n throw error;\n }\n\n async function launchAppOptionally() {\n try {\n await launchAppWithDeviceCtl(udid, bundleIdentifier);\n } catch (error: any) {\n if (indicator) {\n indicator.fail();\n }\n if (error.code === 'APPLE_DEVICE_LOCKED') {\n // Get the app name from the binary path.\n const appName = path.basename(bundle).split('.')[0] ?? 'app';\n if (\n isInteractive() &&\n (await confirmAsync({\n message: `Cannot launch ${appName} because the device is locked. Unlock ${deviceName} to continue...`,\n initial: true,\n }))\n ) {\n return launchAppOptionally();\n }\n throw new CommandError(\n `Cannot launch ${appName} on ${deviceName} because the device is locked.`\n );\n }\n throw error;\n }\n }\n\n await launchAppOptionally();\n}\n"],"names":["devicectlAsync","getConnectedAppleDevicesAsync","hasDevicectlEverBeenInstalled","installAndLaunchAppAsync","launchAppWithDeviceCtl","launchBinaryOnMacAsync","DEVICE_CTL_EXISTS_PATH","path","join","getExpoHomeDirectory","debug","require","args","options","xcrunAsync","error","CommandError","errorCodes","getDeviceCtlErrorCodes","stderr","includes","tmpPath","createTempFilePath","devices","stdout","devicesJson","JsonFile","readAsync","info","jsonVersion","Log","warn","assertDevicesJson","result","results","assert","Array","isArray","JSON","stringify","bundleId","appBinaryPath","spawnAsync","code","message","installAppWithDeviceCtlAsync","uuid","bundleIdOrAppPath","onProgress","Promise","resolve","reject","childProcess","spawn","currentProgress","hasStarted","updateProgress","progress","isComplete","status","on","data","strings","toString","split","EOL","map","value","trim","forEach","str","match","parseInt","read","err","Error","detach","off","kill","installExitHooks","deviceId","log","matchAll","_line","hasEverBeenInstalled","fs","existsSync","isInstalled","isDevicectlInstalled","writeFileSync","execSync","stdio","props","bundle","bundleIdentifier","udid","deviceName","indicator","ora","start","text","chalk","bold","succeed","fail","launchAppOptionally","appName","basename","isInteractive","confirmAsync","initial"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../src/start/platforms/ios/devicectl.ts"],"sourcesContent":["/**\n * Copyright © 2024 650 Industries.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nimport JsonFile from '@expo/json-file';\nimport spawnAsync, { SpawnOptions, SpawnResult } from '@expo/spawn-async';\nimport chalk from 'chalk';\nimport { spawn, execSync } from 'child_process';\nimport fs from 'fs';\nimport assert from 'node:assert';\nimport { Ora } from 'ora';\nimport { EOL } from 'os';\nimport path from 'path';\n\nimport { xcrunAsync } from './xcrun';\nimport { getExpoHomeDirectory } from '../../../api/user/UserSettings';\nimport * as Log from '../../../log';\nimport { createTempFilePath } from '../../../utils/createTempPath';\nimport { CommandError } from '../../../utils/errors';\nimport { installExitHooks } from '../../../utils/exit';\nimport { isInteractive } from '../../../utils/interactive';\nimport { ora } from '../../../utils/ora';\nimport { confirmAsync } from '../../../utils/prompts';\n\nconst DEVICE_CTL_EXISTS_PATH = path.join(getExpoHomeDirectory(), 'devicectl-exists');\n\nconst debug = require('debug')('expo:devicectl') as typeof console.log;\n\ntype AnyEnum<T extends string = string> = T | (string & object);\n\ntype DeviceCtlDevice = {\n capabilities: DeviceCtlDeviceCapability[];\n connectionProperties: DeviceCtlConnectionProperties;\n deviceProperties: DeviceCtlDeviceProperties;\n hardwareProperties: DeviceCtlHardwareProperties;\n /** \"A1A1AAA1-0011-1AA1-11A1-10A1111AA11A\" */\n identifier: string;\n visibilityClass: AnyEnum<'default'>;\n};\n\ntype DeviceCtlHardwareProperties = {\n cpuType: DeviceCtlCpuType;\n deviceType: AnyEnum<'iPhone'>;\n /** 1114404411111111 */\n ecid: number;\n /** \"D74AP\" */\n hardwareModel: string;\n /** 512000000000 */\n internalStorageCapacity: number;\n /** true */\n isProductionFused: boolean;\n /** \"iPhone 14 Pro Max\" */\n marketingName: string;\n /** \"iOS\" */\n platform: AnyEnum<'iOS' | 'xrOS'>;\n /** \"iPhone15,3\" */\n productType: AnyEnum<'iPhone13,4' | 'iPhone15,3'>;\n reality: AnyEnum<'physical'>;\n /** \"X2X1CC1XXX\" */\n serialNumber: string;\n supportedCPUTypes: DeviceCtlCpuType[];\n /** [1] */\n supportedDeviceFamilies: number[];\n thinningProductType: AnyEnum<'iPhone15,3'>;\n /** \"00001110-001111110110101A\" */\n udid: string;\n};\n\ntype DeviceCtlDeviceProperties = {\n /** true */\n bootedFromSnapshot: boolean;\n /** \"com.apple.os.update-AD0CF111ACFF11A11111A76A3D1262AE42A3F56F305AF5AE1135393A7A14A7D1\" */\n bootedSnapshotName: string;\n /** false */\n ddiServicesAvailable: boolean;\n\n developerModeStatus: AnyEnum<'enabled'>;\n /** false */\n hasInternalOSBuild: boolean;\n /** \"Evan's phone\" */\n name: string;\n /** \"21E236\" */\n osBuildUpdate: string;\n /** \"17.4.1\" */\n osVersionNumber: string;\n /** false */\n rootFileSystemIsWritable: boolean;\n};\n\ntype DeviceCtlDeviceCapability =\n | {\n name: AnyEnum;\n featureIdentifier: AnyEnum;\n }\n | {\n featureIdentifier: 'com.apple.coredevice.feature.connectdevice';\n name: 'Connect to Device';\n }\n | {\n featureIdentifier: 'com.apple.coredevice.feature.unpairdevice';\n name: 'Unpair Device';\n }\n | {\n featureIdentifier: 'com.apple.coredevice.feature.acquireusageassertion';\n name: 'Acquire Usage Assertion';\n };\n\ntype DeviceCtlConnectionProperties = {\n authenticationType: AnyEnum<'manualPairing'>;\n isMobileDeviceOnly: boolean;\n /** \"2024-04-20T22:50:04.244Z\" */\n lastConnectionDate: string;\n pairingState: AnyEnum<'paired'>;\n /** [\"00001111-001111110110101A.coredevice.local\", \"A1A1AAA1-0011-1AA1-11A1-10A1111AA11A.coredevice.local\"] */\n potentialHostnames: string[];\n transportType: AnyEnum<'localNetwork' | 'wired'>;\n tunnelState: AnyEnum<'disconnected' | 'unavailable'>;\n tunnelTransportProtocol: AnyEnum<'tcp'>;\n};\n\ntype DeviceCtlCpuType = {\n name: AnyEnum<'arm64e' | 'arm64' | 'arm64_32'>;\n subType: number;\n /** 16777228 */\n type: number;\n};\n\n/** Run a `devicectl` command. */\nexport async function devicectlAsync(\n args: (string | undefined)[],\n options?: SpawnOptions\n): Promise<SpawnResult> {\n try {\n return await xcrunAsync(['devicectl', ...args], options);\n } catch (error: any) {\n if (error instanceof CommandError) {\n throw error;\n }\n if ('stderr' in error) {\n const errorCodes = getDeviceCtlErrorCodes(error.stderr);\n if (errorCodes.includes('Locked')) {\n throw new CommandError('APPLE_DEVICE_LOCKED', 'Device is locked, unlock and try again.');\n }\n }\n throw error;\n }\n}\n\nexport async function getConnectedAppleDevicesAsync() {\n if (!hasDevicectlEverBeenInstalled()) {\n debug('devicectl not found, skipping remote Apple devices.');\n return [];\n }\n\n const tmpPath = createTempFilePath();\n const devices = await devicectlAsync([\n 'list',\n 'devices',\n '--json-output',\n tmpPath,\n // Give two seconds before timing out: between 5 and 9223372036854775807\n '--timeout',\n '5',\n ]);\n debug(devices.stdout);\n const devicesJson = await JsonFile.readAsync(tmpPath);\n\n if (![2, 3].includes((devicesJson as any)?.info?.jsonVersion)) {\n Log.warn(\n 'Unexpected devicectl JSON version output from devicectl. Connecting to physical Apple devices may not work as expected.'\n );\n }\n\n assertDevicesJson(devicesJson);\n\n return devicesJson.result.devices as DeviceCtlDevice[];\n}\n\nfunction assertDevicesJson(\n results: any\n): asserts results is { result: { devices: DeviceCtlDevice[] } } {\n assert(\n results != null && 'result' in results && Array.isArray(results?.result?.devices),\n 'Malformed JSON output from devicectl: ' + JSON.stringify(results, null, 2)\n );\n}\n\nexport async function launchBinaryOnMacAsync(\n bundleId: string,\n appBinaryPath: string\n): Promise<void> {\n const args = ['-b', bundleId, appBinaryPath];\n try {\n await spawnAsync('open', args);\n } catch (error: any) {\n if ('code' in error) {\n if (error.code === 1) {\n throw new CommandError(\n 'MACOS_LAUNCH',\n 'Failed to launch the compatible binary on macOS: open ' +\n args.join(' ') +\n '\\n\\n' +\n error.message\n );\n }\n }\n throw error;\n }\n}\n\nasync function installAppWithDeviceCtlAsync(\n uuid: string,\n bundleIdOrAppPath: string,\n onProgress: (event: { status: string; isComplete: boolean; progress: number }) => void\n): Promise<void> {\n // 𝝠 xcrun devicectl device install app --device 00001110-001111110110101A /Users/evanbacon/Library/Developer/Xcode/DerivedData/Router-hgbqaxzhrhkiftfweydvhgttadvn/Build/Products/Debug-iphoneos/Router.app --verbose\n return new Promise((resolve, reject) => {\n const args: string[] = [\n 'devicectl',\n 'device',\n 'install',\n 'app',\n '--device',\n uuid,\n bundleIdOrAppPath,\n ];\n const childProcess = spawn('xcrun', args);\n debug('xcrun ' + args.join(' '));\n\n let currentProgress = 0;\n let hasStarted = false;\n\n function updateProgress(progress: number) {\n hasStarted = true;\n if (progress <= currentProgress) {\n return;\n }\n currentProgress = progress;\n onProgress({\n progress,\n isComplete: progress === 100,\n status: 'Installing',\n });\n }\n\n childProcess.stdout.on('data', (data: Buffer) => {\n // Sometimes more than one chunk comes at a time, here we split by system newline,\n // then trim and filter.\n const strings = data\n .toString()\n .split(EOL)\n .map((value) => value.trim());\n\n strings.forEach((str) => {\n // Match the progress percentage:\n // - '34%... 35%...' -> 34\n // - '31%...' -> 31\n // - 'Complete!' -> 100\n\n const match = str.match(/(\\d+)%\\.\\.\\./);\n if (match) {\n updateProgress(parseInt(match[1], 10));\n } else if (hasStarted) {\n updateProgress(100);\n }\n });\n\n debug('[stdout]:', strings);\n });\n\n childProcess.on('close', (code) => {\n debug('[close]: ' + code);\n if (code === 0) {\n resolve();\n } else {\n const stderr = childProcess.stderr.read();\n const err = new Error(stderr);\n (err as any).code = code;\n detach(err);\n }\n });\n\n const detach = async (err?: Error) => {\n off?.();\n if (childProcess) {\n return new Promise<void>((resolve) => {\n childProcess?.on('close', resolve);\n childProcess?.kill();\n // childProcess = null;\n reject(err ?? new CommandError('detached'));\n });\n }\n };\n\n const off = installExitHooks(() => detach());\n });\n}\n\nexport async function launchAppWithDeviceCtl(deviceId: string, bundleId: string) {\n await devicectlAsync(['device', 'process', 'launch', '--device', deviceId, bundleId]);\n}\n\n/** Find all error codes from the output log */\nfunction getDeviceCtlErrorCodes(log: string): string[] {\n return [...log.matchAll(/BSErrorCodeDescription\\s+=\\s+(.*)$/gim)].map(([_line, code]) => code);\n}\n\nlet hasEverBeenInstalled: boolean | undefined;\n\nexport function hasDevicectlEverBeenInstalled() {\n if (hasEverBeenInstalled) return hasEverBeenInstalled;\n // It doesn't appear possible for devicectl to ever be uninstalled. We can just check once and store this result forever\n // to prevent cold boots of devicectl from slowing down all invocations of `expo run ios`\n if (fs.existsSync(DEVICE_CTL_EXISTS_PATH)) {\n hasEverBeenInstalled = true;\n return true;\n }\n\n const isInstalled = isDevicectlInstalled();\n\n if (isInstalled) {\n fs.writeFileSync(DEVICE_CTL_EXISTS_PATH, '1');\n }\n hasEverBeenInstalled = isInstalled;\n return isInstalled;\n}\n\nfunction isDevicectlInstalled() {\n try {\n execSync('xcrun devicectl --version', { stdio: 'ignore' });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Wraps the apple device method for installing and running an app,\n * adds indicator and retry loop for when the device is locked.\n */\nexport async function installAndLaunchAppAsync(props: {\n bundle: string;\n bundleIdentifier: string;\n udid: string;\n deviceName: string;\n}): Promise<void> {\n debug('Running on device:', props);\n const { bundle, bundleIdentifier, udid, deviceName } = props;\n let indicator: Ora | undefined;\n\n try {\n if (!indicator) {\n indicator = ora(`Connecting to: ${props.deviceName}`).start();\n }\n\n await installAppWithDeviceCtlAsync(\n udid,\n bundle,\n ({\n status,\n isComplete,\n progress,\n }: {\n status: string;\n isComplete: boolean;\n progress: number;\n }) => {\n if (!indicator) {\n indicator = ora(status).start();\n }\n indicator.text = `${chalk.bold(status)} ${progress}%`;\n if (isComplete) {\n indicator.succeed();\n }\n }\n );\n } catch (error: any) {\n if (indicator) {\n indicator.fail();\n }\n throw error;\n }\n\n async function launchAppOptionally() {\n try {\n await launchAppWithDeviceCtl(udid, bundleIdentifier);\n } catch (error: any) {\n if (indicator) {\n indicator.fail();\n }\n if (error.code === 'APPLE_DEVICE_LOCKED') {\n // Get the app name from the binary path.\n const appName = path.basename(bundle).split('.')[0] ?? 'app';\n if (\n isInteractive() &&\n (await confirmAsync({\n message: `Cannot launch ${appName} because the device is locked. Unlock ${deviceName} to continue...`,\n initial: true,\n }))\n ) {\n return launchAppOptionally();\n }\n throw new CommandError(\n `Cannot launch ${appName} on ${deviceName} because the device is locked.`\n );\n }\n throw error;\n }\n }\n\n await launchAppOptionally();\n}\n"],"names":["devicectlAsync","getConnectedAppleDevicesAsync","hasDevicectlEverBeenInstalled","installAndLaunchAppAsync","launchAppWithDeviceCtl","launchBinaryOnMacAsync","DEVICE_CTL_EXISTS_PATH","path","join","getExpoHomeDirectory","debug","require","args","options","xcrunAsync","error","CommandError","errorCodes","getDeviceCtlErrorCodes","stderr","includes","tmpPath","createTempFilePath","devices","stdout","devicesJson","JsonFile","readAsync","info","jsonVersion","Log","warn","assertDevicesJson","result","results","assert","Array","isArray","JSON","stringify","bundleId","appBinaryPath","spawnAsync","code","message","installAppWithDeviceCtlAsync","uuid","bundleIdOrAppPath","onProgress","Promise","resolve","reject","childProcess","spawn","currentProgress","hasStarted","updateProgress","progress","isComplete","status","on","data","strings","toString","split","EOL","map","value","trim","forEach","str","match","parseInt","read","err","Error","detach","off","kill","installExitHooks","deviceId","log","matchAll","_line","hasEverBeenInstalled","fs","existsSync","isInstalled","isDevicectlInstalled","writeFileSync","execSync","stdio","props","bundle","bundleIdentifier","udid","deviceName","indicator","ora","start","text","chalk","bold","succeed","fail","launchAppOptionally","appName","basename","isInteractive","confirmAsync","initial"],"mappings":"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"}
@@ -837,6 +837,7 @@ class MetroBundlerDevServer extends _BundlerDevServer.BundlerDevServer {
837
837
  };
838
838
  this.instanceMetroOptions = instanceMetroOptions;
839
839
  const parsedOptions = {
840
+ host: options.location.hostType === 'localhost' ? 'localhost' : undefined,
840
841
  port: options.port,
841
842
  maxWorkers: options.maxWorkers,
842
843
  resetCache: options.resetDevServer