mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-15 03:38:08 -10:00
[eas-cli] Clear the simulator session file when start fails before ready
When the session errors or times out before it is ready and the stop is confirmed, reset .env.eas-simulator so simulator:exec does not report the session as still starting. Log the saved path only after the interrupt check, and shorten the changeset. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
5e3a735952
commit
94ab208fd1
4 files changed
+61
-7
No files matched your search
@@ -2,4 +2,4 @@
|
||||
'eas-cli': patch
|
||||
---
|
||||
|
||||
Make `eas simulator:exec` fail, instead of running the command against a local device, when `.env.eas-simulator` has no session or the session is still starting; sessions started with `--out-config-type env` write no file, so run their commands without `eas simulator:exec`. Print the full path of `.env.eas-simulator` in `eas simulator:start`.
|
||||
Make `eas simulator:exec` fail when `.env.eas-simulator` has no ready session, instead of running the command against a local device.
|
||||
@@ -2834,6 +2834,34 @@ describe(Simulator, () => {
|
||||
|
||||
await expect(command.runAsync()).rejects.toThrow(/errored before the .* session was ready/);
|
||||
expect(mockEnsureDeviceRunSessionStoppedAsync).toHaveBeenCalled();
|
||||
// The file must not keep saying that the session is starting.
|
||||
expect(mockResetSimulatorEnvAsync).toHaveBeenCalledWith(projectDir, 'session-123');
|
||||
});
|
||||
|
||||
it('keeps the dotenv file when it cannot confirm that a session which errored before it was ready stopped', async () => {
|
||||
mockByIdAsync.mockResolvedValue(
|
||||
makeDeviceRunSession({ status: DeviceRunSessionStatus.Errored, remoteConfig: null })
|
||||
);
|
||||
mockEnsureDeviceRunSessionStoppedAsync.mockRejectedValue(new Error('network failed'));
|
||||
|
||||
const { command } = createCommand(['--platform', 'ios', '--non-interactive']);
|
||||
|
||||
await expect(command.runAsync()).rejects.toThrow(/errored before the .* session was ready/);
|
||||
expect(mockResetSimulatorEnvAsync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('keeps the original error when it cannot clean up the dotenv file of a session that errored before it was ready', async () => {
|
||||
mockByIdAsync.mockResolvedValue(
|
||||
makeDeviceRunSession({ status: DeviceRunSessionStatus.Errored, remoteConfig: null })
|
||||
);
|
||||
mockResetSimulatorEnvAsync.mockRejectedValue(new Error('read-only file system'));
|
||||
|
||||
const { command } = createCommand(['--platform', 'ios', '--non-interactive']);
|
||||
|
||||
await expect(command.runAsync()).rejects.toThrow(/errored before the .* session was ready/);
|
||||
expect(Log.warn).toHaveBeenCalledWith(
|
||||
`The session stopped, but ${simulatorDotenvPath} could not be cleaned up: read-only file system`
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when the turtle job run finishes before the session is ready', async () => {
|
||||
@@ -3172,6 +3200,7 @@ describe(Simulator, () => {
|
||||
|
||||
await expect(command.runAsync()).rejects.toThrow(/Timed out after \d+s waiting for/);
|
||||
expect(mockEnsureDeviceRunSessionStoppedAsync).toHaveBeenCalled();
|
||||
expect(mockResetSimulatorEnvAsync).toHaveBeenCalledWith(projectDir, 'session-123');
|
||||
});
|
||||
|
||||
it('prints JSON only and skips the human-readable output with --json', async () => {
|
||||
|
||||
@@ -55,7 +55,8 @@ export default class SimulatorExec extends EasCommand {
|
||||
if (simulatorEnv[EAS_SIMULATOR_SESSION_STATUS] === EAS_SIMULATOR_SESSION_STATUS_STARTING) {
|
||||
throw new Error(
|
||||
`Simulator session ${sessionId} in ${simulatorEnvFilePath} is still starting. ` +
|
||||
'Wait until `eas simulator:start` reports that the session is ready, then run this command again.'
|
||||
'Wait until `eas simulator:start` reports that the session is ready, then run this command again. ' +
|
||||
'If `eas simulator:start` is no longer running, start a new session with `eas simulator:start`.'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -582,7 +582,9 @@ With local egress, actions wait for the connection and require interactive mode.
|
||||
}
|
||||
} catch (err) {
|
||||
pollSpinner.fail(`Failed while polling for ${flags.type} session to be ready`);
|
||||
await ensureDeviceRunSessionStoppedSafelyAsync(graphqlClient, deviceRunSessionId);
|
||||
if (await ensureDeviceRunSessionStoppedSafelyAsync(graphqlClient, deviceRunSessionId)) {
|
||||
await resetUnreadySimulatorEnvAsync(projectDir, deviceRunSessionId);
|
||||
}
|
||||
sessionInterrupt.dispose();
|
||||
throw err;
|
||||
}
|
||||
@@ -600,7 +602,9 @@ With local egress, actions wait for the connection and require interactive mode.
|
||||
|
||||
if (!remoteConfig) {
|
||||
pollSpinner.fail(`Timed out waiting for ${flags.type} session to start`);
|
||||
await ensureDeviceRunSessionStoppedSafelyAsync(graphqlClient, deviceRunSessionId);
|
||||
if (await ensureDeviceRunSessionStoppedSafelyAsync(graphqlClient, deviceRunSessionId)) {
|
||||
await resetUnreadySimulatorEnvAsync(projectDir, deviceRunSessionId);
|
||||
}
|
||||
sessionInterrupt.dispose();
|
||||
throw new Error(
|
||||
`Timed out after ${Math.round(STARTUP_TIMEOUT_MS / 1000)}s waiting for ${flags.type} session to start (excluding time in the queue). ${link(deviceRunSessionUrl)}`
|
||||
@@ -835,9 +839,6 @@ With local egress, actions wait for the connection and require interactive mode.
|
||||
if (jsonFlag || imported.config?.actions?.length) {
|
||||
// Keep the previous session's saved credentials until setup succeeds.
|
||||
simulatorEnvWritten = await writeReadySimulatorEnvAsync();
|
||||
if (simulatorEnvWritten && !jsonFlag) {
|
||||
Log.log(`Saved the session configuration to ${getSimulatorEnvFilePath(projectDir)}`);
|
||||
}
|
||||
if (sessionInterrupt.signal.aborted) {
|
||||
await stopDeviceRunSessionAfterInterruptAsync({
|
||||
graphqlClient,
|
||||
@@ -848,6 +849,9 @@ With local egress, actions wait for the connection and require interactive mode.
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (simulatorEnvWritten && !jsonFlag) {
|
||||
Log.log(`Saved the session configuration to ${getSimulatorEnvFilePath(projectDir)}`);
|
||||
}
|
||||
}
|
||||
if (jsonFlag) {
|
||||
sessionInterrupt.dispose();
|
||||
@@ -1118,6 +1122,26 @@ async function stopDeviceRunSessionCreatedAfterCancelAsync(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the dotenv file of a session that stopped before it was ready, so that
|
||||
* `eas simulator:exec` does not report the session as still starting. A failure only warns, so
|
||||
* that the caller can still throw the error that stopped the session.
|
||||
*/
|
||||
async function resetUnreadySimulatorEnvAsync(
|
||||
projectDir: string,
|
||||
deviceRunSessionId: string
|
||||
): Promise<void> {
|
||||
try {
|
||||
await resetSimulatorEnvAsync(projectDir, deviceRunSessionId);
|
||||
} catch (error) {
|
||||
Log.warn(
|
||||
`The session stopped, but ${getSimulatorEnvFilePath(projectDir)} could not be cleaned up: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function resetSimulatorEnvVerboseAsync(
|
||||
projectDir: string,
|
||||
deviceRunSessionId: string
|
||||
|
||||
Reference in new issue
Block a user