@expo/cli 56.1.4 → 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.
- package/build/bin/cli +1 -1
- package/build/src/api/user/actions.js +7 -0
- package/build/src/api/user/actions.js.map +1 -1
- package/build/src/api/user/expoSsoLauncher.js +2 -8
- package/build/src/api/user/expoSsoLauncher.js.map +1 -1
- package/build/src/api/user/user.js +12 -0
- package/build/src/api/user/user.js.map +1 -1
- package/build/src/events/index.js +1 -1
- package/build/src/start/platforms/ios/simctl.js +4 -0
- package/build/src/start/platforms/ios/simctl.js.map +1 -1
- package/build/src/start/server/metro/MetroBundlerDevServer.js +51 -32
- package/build/src/start/server/metro/MetroBundlerDevServer.js.map +1 -1
- package/build/src/start/server/metro/withMetroMultiPlatform.js +8 -4
- package/build/src/start/server/metro/withMetroMultiPlatform.js.map +1 -1
- package/build/src/start/server/type-generation/routes.js +1 -3
- package/build/src/start/server/type-generation/routes.js.map +1 -1
- package/build/src/utils/open.js +243 -11
- package/build/src/utils/open.js.map +1 -1
- package/build/src/utils/telemetry/clients/FetchClient.js +1 -1
- package/build/src/utils/telemetry/utils/context.js +1 -1
- package/package.json +13 -14
- package/static/loading-page/index.html +35 -1
package/build/bin/cli
CHANGED
|
@@ -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":";;;;;;;;;;;
|
|
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,13 +15,6 @@ 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() {
|
|
21
|
-
return data;
|
|
22
|
-
};
|
|
23
|
-
return data;
|
|
24
|
-
}
|
|
25
18
|
function _crypto() {
|
|
26
19
|
const data = /*#__PURE__*/ _interop_require_default(require("crypto"));
|
|
27
20
|
_crypto = function() {
|
|
@@ -38,6 +31,7 @@ function _http() {
|
|
|
38
31
|
}
|
|
39
32
|
const _log = /*#__PURE__*/ _interop_require_wildcard(require("../../log"));
|
|
40
33
|
const _errors = require("../../utils/errors");
|
|
34
|
+
const _open = require("../../utils/open");
|
|
41
35
|
const _client = require("../rest/client");
|
|
42
36
|
function _interop_require_default(obj) {
|
|
43
37
|
return obj && obj.__esModule ? obj : {
|
|
@@ -191,7 +185,7 @@ async function getSessionUsingBrowserAuthFlowAsync({ expoWebsiteUrl, sso = false
|
|
|
191
185
|
const port = address.port;
|
|
192
186
|
const authorizeUrl = buildExpoLoginUrl(port, sso);
|
|
193
187
|
_log.log(`If your browser doesn't automatically open, visit this link to log in: ${authorizeUrl}`);
|
|
194
|
-
(0,
|
|
188
|
+
(0, _open.openBrowserAsync)(authorizeUrl);
|
|
195
189
|
});
|
|
196
190
|
server.on('connection', (connection)=>{
|
|
197
191
|
connections.add(connection);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/api/user/expoSsoLauncher.ts"],"sourcesContent":["import assert from 'assert';\nimport
|
|
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":";;;;;;;;;;;
|
|
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"}
|
|
@@ -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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"}
|
|
@@ -1000,7 +1000,6 @@ class MetroBundlerDevServer extends _BundlerDevServer.BundlerDevServer {
|
|
|
1000
1000
|
metro,
|
|
1001
1001
|
server
|
|
1002
1002
|
}, ({ changes })=>{
|
|
1003
|
-
var _exp_extra_router, _exp_extra;
|
|
1004
1003
|
if (hasApiRoutes) {
|
|
1005
1004
|
// NOTE(EvanBacon): We aren't sure what files the API routes are using so we'll just invalidate
|
|
1006
1005
|
// aggressively to ensure we always have the latest. The only caching we really get here is for
|
|
@@ -1018,12 +1017,6 @@ class MetroBundlerDevServer extends _BundlerDevServer.BundlerDevServer {
|
|
|
1018
1017
|
}
|
|
1019
1018
|
}
|
|
1020
1019
|
}
|
|
1021
|
-
// Handle loader file changes for HMR
|
|
1022
|
-
if ((_exp_extra = exp.extra) == null ? void 0 : (_exp_extra_router = _exp_extra.router) == null ? void 0 : _exp_extra_router.unstable_useServerDataLoaders) {
|
|
1023
|
-
for (const change of changes.modifiedFiles){
|
|
1024
|
-
this.handleLoaderFileChange(change[0]);
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
1020
|
});
|
|
1028
1021
|
}
|
|
1029
1022
|
// If React 19 is enabled, then add RSC middleware to the dev server.
|
|
@@ -1115,6 +1108,11 @@ class MetroBundlerDevServer extends _BundlerDevServer.BundlerDevServer {
|
|
|
1115
1108
|
this.metro = null;
|
|
1116
1109
|
this.hmrServer = null;
|
|
1117
1110
|
this.ssrHmrClients = new Map();
|
|
1111
|
+
for (const unlisten of this.loaderGraphListeners.values()){
|
|
1112
|
+
unlisten();
|
|
1113
|
+
}
|
|
1114
|
+
this.loaderGraphListeners.clear();
|
|
1115
|
+
this.pendingLoaderInvalidationChangeIds.clear();
|
|
1118
1116
|
callback == null ? void 0 : callback(err);
|
|
1119
1117
|
});
|
|
1120
1118
|
};
|
|
@@ -1348,8 +1346,6 @@ class MetroBundlerDevServer extends _BundlerDevServer.BundlerDevServer {
|
|
|
1348
1346
|
isLoaderBundle: true
|
|
1349
1347
|
});
|
|
1350
1348
|
if (routeModule.loader) {
|
|
1351
|
-
// Register this module for loader HMR
|
|
1352
|
-
this.setupLoaderHmr(modulePath);
|
|
1353
1349
|
const maybeResponse = await routeModule.loader(request, route.params);
|
|
1354
1350
|
let data;
|
|
1355
1351
|
if (maybeResponse instanceof Response) {
|
|
@@ -1426,29 +1422,48 @@ class MetroBundlerDevServer extends _BundlerDevServer.BundlerDevServer {
|
|
|
1426
1422
|
};
|
|
1427
1423
|
this.registerSsrHmrAsync(url.toString(), onReload);
|
|
1428
1424
|
}
|
|
1429
|
-
|
|
1430
|
-
|
|
1425
|
+
// Subscribe to a loader bundle's Metro graph so we can broadcast `loader-invalidate` and
|
|
1426
|
+
// drop the SSR module eval cache when the loader's dependency graph is dirtied. This avoids
|
|
1427
|
+
// the full-page reload the old stub used and keeps client state across loader edits.
|
|
1428
|
+
setupLoaderGraphListener(graphId, resolvedEntryFilePath, graph) {
|
|
1429
|
+
if (this.loaderGraphListeners.has(graphId) || !this.metro) {
|
|
1431
1430
|
return;
|
|
1432
1431
|
}
|
|
1433
|
-
this.
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
'.jsx',
|
|
1442
|
-
'.js'
|
|
1443
|
-
];
|
|
1444
|
-
const isLoaderFile = possibleExtensions.some((ext)=>changedFilePath === loaderPath + ext || changedFilePath === loaderPath);
|
|
1445
|
-
if (isLoaderFile) {
|
|
1446
|
-
debug('[Loader HMR] Loader file changed, triggering reload:', changedFilePath);
|
|
1447
|
-
this.broadcastMessage('sendDevCommand', {
|
|
1448
|
-
name: 'reload'
|
|
1449
|
-
});
|
|
1432
|
+
const deltaBundler = this.metro.getBundler().getDeltaBundler();
|
|
1433
|
+
const onChange = async (changeEvent)=>{
|
|
1434
|
+
debug('[Loader HMR] Graph change detected for:', resolvedEntryFilePath);
|
|
1435
|
+
if (!this.shouldBroadcastLoaderInvalidation(changeEvent == null ? void 0 : changeEvent.changeId)) {
|
|
1436
|
+
return;
|
|
1437
|
+
}
|
|
1438
|
+
if (typeof globalThis.__c === 'function') {
|
|
1439
|
+
globalThis.__c();
|
|
1450
1440
|
}
|
|
1441
|
+
this.broadcastMessage('sendDevCommand', {
|
|
1442
|
+
name: 'loader-invalidate',
|
|
1443
|
+
data: {
|
|
1444
|
+
scope: 'all'
|
|
1445
|
+
}
|
|
1446
|
+
});
|
|
1447
|
+
};
|
|
1448
|
+
const unlisten = deltaBundler.listen(graph, onChange);
|
|
1449
|
+
this.loaderGraphListeners.set(graphId, unlisten);
|
|
1450
|
+
}
|
|
1451
|
+
shouldBroadcastLoaderInvalidation(changeId) {
|
|
1452
|
+
if (!changeId) {
|
|
1453
|
+
return true;
|
|
1454
|
+
}
|
|
1455
|
+
if (this.pendingLoaderInvalidationChangeIds.has(changeId)) {
|
|
1456
|
+
return false;
|
|
1451
1457
|
}
|
|
1458
|
+
this.pendingLoaderInvalidationChangeIds.add(changeId);
|
|
1459
|
+
// All listeners for a single filesystem change are dispatched synchronously from one
|
|
1460
|
+
// DeltaCalculator EventEmitter.emit(), so dedupe collisions resolve within microseconds.
|
|
1461
|
+
// The timeout exists only to bound memory: without it, this Set would accumulate one entry
|
|
1462
|
+
// per change for the lifetime of the dev server.
|
|
1463
|
+
setTimeout(()=>{
|
|
1464
|
+
this.pendingLoaderInvalidationChangeIds.delete(changeId);
|
|
1465
|
+
}, 500);
|
|
1466
|
+
return true;
|
|
1452
1467
|
}
|
|
1453
1468
|
// Direct Metro access
|
|
1454
1469
|
// Emulates the Metro dev server .bundle endpoint without having to go through a server.
|
|
@@ -1495,13 +1510,14 @@ class MetroBundlerDevServer extends _BundlerDevServer.BundlerDevServer {
|
|
|
1495
1510
|
type: 'bundle_build_started'
|
|
1496
1511
|
});
|
|
1497
1512
|
try {
|
|
1513
|
+
var _transformOptions_customTransformOptions;
|
|
1498
1514
|
let delta;
|
|
1499
1515
|
let revision;
|
|
1500
1516
|
try {
|
|
1501
|
-
var
|
|
1517
|
+
var _transformOptions_customTransformOptions1;
|
|
1502
1518
|
// TODO: Some bug in Metro/RSC causes this to break when changing imports in server components.
|
|
1503
1519
|
// We should resolve the bug because it results in ~6x faster bundling to reuse the graph revision.
|
|
1504
|
-
if (((
|
|
1520
|
+
if (((_transformOptions_customTransformOptions1 = transformOptions.customTransformOptions) == null ? void 0 : _transformOptions_customTransformOptions1.environment) === 'react-server') {
|
|
1505
1521
|
const props = await this.metro.getBundler().initializeGraph(// NOTE: Using absolute path instead of relative input path is a breaking change.
|
|
1506
1522
|
// entryFile,
|
|
1507
1523
|
resolvedEntryFilePath, transformOptions, resolverOptions, {
|
|
@@ -1527,6 +1543,9 @@ class MetroBundlerDevServer extends _BundlerDevServer.BundlerDevServer {
|
|
|
1527
1543
|
(0, _metroErrorInterface.dropStackIfContainsCodeFrame)(error);
|
|
1528
1544
|
throw error;
|
|
1529
1545
|
}
|
|
1546
|
+
if (!this.instanceMetroOptions.isExporting && transformOptions.dev !== false && ((_transformOptions_customTransformOptions = transformOptions.customTransformOptions) == null ? void 0 : _transformOptions_customTransformOptions.isLoaderBundle) === 'true') {
|
|
1547
|
+
this.setupLoaderGraphListener(revision.graphId, resolvedEntryFilePath, revision.graph);
|
|
1548
|
+
}
|
|
1530
1549
|
bundlePerfLogger == null ? void 0 : bundlePerfLogger.annotate({
|
|
1531
1550
|
int: {
|
|
1532
1551
|
graph_node_count: revision.graph.dependencies.size
|
|
@@ -1658,7 +1677,7 @@ class MetroBundlerDevServer extends _BundlerDevServer.BundlerDevServer {
|
|
|
1658
1677
|
});
|
|
1659
1678
|
}
|
|
1660
1679
|
constructor(...args){
|
|
1661
|
-
super(...args), this.metro = null, this.hmrServer = null, this.ssrHmrClients = new Map(), // Set when the server is started.
|
|
1680
|
+
super(...args), this.metro = null, this.hmrServer = null, this.ssrHmrClients = new Map(), this.loaderGraphListeners = new Map(), this.pendingLoaderInvalidationChangeIds = new Set(), // Set when the server is started.
|
|
1662
1681
|
this.instanceMetroOptions = {}, this.ssrLoadModule = async (filePath, specificOptions = {}, extras = {})=>{
|
|
1663
1682
|
// NOTE(@kitten): We don't properly initialize the server-side modules
|
|
1664
1683
|
// Instead, we first load an entrypoint with an empty bundle to initialize the runtime instead
|
|
@@ -1678,7 +1697,7 @@ class MetroBundlerDevServer extends _BundlerDevServer.BundlerDevServer {
|
|
|
1678
1697
|
}
|
|
1679
1698
|
return (0, _getStaticRenderFunctions.evalMetroAndWrapFunctions)(this.projectRoot, res.src, res.filename, res.map, specificOptions.isExporting ?? this.instanceMetroOptions.isExporting);
|
|
1680
1699
|
}, this.rscRenderer = null, this.onReloadRscEvent = null, // API Routes
|
|
1681
|
-
this.pendingRouteOperations = new Map()
|
|
1700
|
+
this.pendingRouteOperations = new Map();
|
|
1682
1701
|
}
|
|
1683
1702
|
}
|
|
1684
1703
|
function getBuildID(buildNumber) {
|