Compare commits

...
5 Commits
11 changed files with 212 additions and 213 deletions

No files matched your search

+7 -1
View File
@@ -10,10 +10,16 @@ This is the log of notable changes to EAS CLI and related packages.
### 🐛 Bug fixes
- [eas-cli] Stop telling interactive `eas simulator:start --egress local` users to run `eas simulator:egress` when the egress client already runs in the same terminal. ([#4382](https://github.com/expo/eas-cli/pull/4382) by [@brentvatne](https://github.com/brentvatne))
- [eas-cli] Shorten the local egress banner printed by `eas simulator:start` and stop mentioning `eas simulator:egress` in interactive mode, where the client runs in the same terminal. ([#4383](https://github.com/expo/eas-cli/pull/4383) by [@brentvatne](https://github.com/brentvatne))
### 🧹 Chores
## [24.1.1](https://github.com/expo/eas-cli/releases/tag/v24.1.1) - 2026-09-11
### 🐛 Bug fixes
- [eas-cli] Stop telling interactive `eas simulator:start --egress local` users to run `eas simulator:egress` when the egress client already runs in the same terminal. ([#4382](https://github.com/expo/eas-cli/pull/4382) by [@brentvatne](https://github.com/brentvatne))
## [24.1.0](https://github.com/expo/eas-cli/releases/tag/v24.1.0) - 2026-09-10
### 🎉 New features
+2 -2
View File
@@ -1,4 +1,4 @@
{
"STAGING": "24.1.0",
"PRODUCTION": "24.1.0"
"STAGING": "24.1.1",
"PRODUCTION": "24.1.1"
}
+1 -1
View File
@@ -1,5 +1,5 @@
{
"npmClient": "yarn",
"version": "24.1.1",
"version": "24.1.2",
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
}
+131 -131
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "eas-cli",
"version": "24.1.1",
"version": "24.1.2",
"description": "EAS command line tool",
"keywords": [
"cli",
@@ -976,11 +976,17 @@ describe(Simulator, () => {
await command.runAsync();
expect(fs.writeFile).not.toHaveBeenCalled();
expect(Log.log).toHaveBeenCalledWith(
expect.stringContaining('Start `eas simulator:egress --config-type env` in another process')
expect.stringContaining(
'Run the egress client to connect the tunnel:\n\neas simulator:egress --config-type env\n'
)
);
expect(Log.log).not.toHaveBeenCalledWith(
expect.stringContaining('Start `eas simulator:egress` in another process')
expect.stringContaining(
'Run the egress client to connect the tunnel:\n\neas simulator:egress\n'
)
);
// The instructions block is the only place the command is mentioned.
expect(Log.log).not.toHaveBeenCalledWith(expect.stringContaining('in another process'));
});
it.each(localEgressSessions)(
@@ -59,9 +59,7 @@ export default class SimulatorEgress extends EasCommand {
...egress,
signal: abortController.signal,
onConnected: () => {
Log.succeed(
"Egress tunnel connected. Proxied HTTP(S) requests can use this machine's network."
);
Log.succeed('Egress tunnel connected.');
},
onDisconnected: () => {
Log.warn(
@@ -430,11 +430,7 @@ export default class Simulator extends EasCommand {
if (nonInteractive) {
sessionInterrupt.dispose();
if (localEgress) {
Log.log(
`Start \`eas simulator:egress${flags['out-config-type'] === OUT_CONFIG_TYPE_VALUES.Env ? ' --config-type env' : ''}\` in another process to connect the tunnel for proxied HTTP(S) requests.`
);
}
// The instructions above already tell the reader to run eas simulator:egress.
Log.log(
`When you are done, stop the session with: eas simulator:stop --id ${deviceRunSessionId}`
);
@@ -447,8 +443,6 @@ export default class Simulator extends EasCommand {
let egressError: Error | undefined;
const egressAbortController = new AbortController();
if (localEgress) {
Log.log('Press Ctrl+C to stop both the egress client and the simulator session.');
Log.newLine();
sessionInterrupt.signal.addEventListener(
'abort',
() => {
@@ -460,9 +454,7 @@ export default class Simulator extends EasCommand {
...localEgress,
signal: egressAbortController.signal,
onConnected: () => {
Log.succeed(
"Egress tunnel connected. Proxied HTTP(S) requests can use this machine's network."
);
Log.succeed('Egress tunnel connected.');
},
onDisconnected: () => {
Log.warn(
@@ -61,31 +61,35 @@ describe('local egress configuration', () => {
EAS_SIMULATOR_EGRESS_ALLOW: '',
});
const instructions = formatRemoteSessionInstructions(agentDeviceConfigWithEgress, 'dotenv');
expect(instructions).toContain('eas simulator:egress');
expect(instructions).toContain(
"🔀 Local egress: the simulator's HTTP(S) traffic exits from this machine.\n"
);
expect(instructions).toContain('Run the egress client to connect the tunnel');
expect(instructions).toContain('eas simulator:egress');
expect(instructions).toContain('Keep it running for the life of the session.');
expect(instructions).not.toContain('may reach');
expect(instructions).not.toContain('It can also reach');
expect(formatRemoteSessionInstructions(agentDeviceConfigWithEgress, 'env')).toContain(
"export EAS_SIMULATOR_EGRESS_ALLOW=''"
);
});
it('describes the inline egress client instead of asking the reader to start one', () => {
const instructions = formatRemoteSessionInstructions(agentDeviceConfigWithEgress, 'dotenv', {
egressClientRunsInline: true,
});
expect(instructions).toContain('The egress client runs in this terminal');
expect(instructions).toContain(
'reconnect the tunnel from another shell with:\n\neas simulator:egress'
);
expect(instructions).not.toContain('Run the egress client to connect the tunnel');
expect(instructions).not.toContain('Keep it running');
expect(
formatRemoteSessionInstructions(agentDeviceConfigWithEgress, 'env', {
egressClientRunsInline: true,
})
).toContain('eas simulator:egress --config-type env');
it('says nothing about starting the client when it runs inline', () => {
for (const configType of ['dotenv', 'env'] as const) {
const instructions = formatRemoteSessionInstructions(
agentDeviceConfigWithEgress,
configType,
{
egressClientRunsInline: true,
}
);
expect(instructions).toContain(
"🔀 Local egress: the simulator's HTTP(S) traffic exits from this machine."
);
expect(instructions).not.toContain('eas simulator:egress');
expect(instructions).not.toContain('Run the egress client');
expect(instructions).not.toContain('Keep it running');
expect(instructions).not.toContain('export EAS_SIMULATOR_EGRESS');
}
});
it('carries the allowed local destinations into the config, env file and instructions', () => {
@@ -103,19 +107,19 @@ describe('local egress configuration', () => {
egressAllow,
});
expect(instructions).toContain(
"The simulator may reach localhost:3000, 192.168.1.20:8080 on this machine's network."
);
expect(instructions).toContain(
'127.0.0.1:3000 in the simulator reaches the same port on this machine (like adb reverse)'
"🔀 Local egress: the simulator's HTTP(S) traffic exits from this machine. It can also reach localhost:3000, 192.168.1.20:8080."
);
expect(instructions).not.toContain('reachable by name only');
});
it('omits the loopback forwarding notice when no allowed destination is loopback', () => {
it('warns only about loopback entries the device host will not forward', () => {
expect(
formatRemoteSessionInstructions(agentDeviceConfigWithEgress, 'dotenv', {
egressAllow: ['192.168.1.20:8080'],
egressAllow: ['localhost:80', 'localhost:3000'],
})
).not.toContain('adb reverse');
).toContain(
'localhost:80 is reachable by name only: privileged ports and the egress proxy port are not forwarded to 127.0.0.1 in the simulator.'
);
});
});
@@ -141,13 +145,13 @@ describe(getLoopbackForwardPlan, () => {
});
describe(formatLoopbackForwardNotice, () => {
it('describes forwarded ports and name-only entries', () => {
it('describes name-only entries and stays silent about forwarded ports', () => {
expect(formatLoopbackForwardNotice({ ports: [3000, 8082], skipped: ['localhost:80'] })).toEqual(
[
'127.0.0.1:3000, 127.0.0.1:8082 in the simulator reach the same ports on this machine (like adb reverse), so dev server URLs that use 127.0.0.1 work.',
'localhost:80 is reachable by name only: privileged ports and the egress proxy port are not forwarded to 127.0.0.1 in the simulator.',
]
);
expect(formatLoopbackForwardNotice({ ports: [3000], skipped: [] })).toEqual([]);
expect(formatLoopbackForwardNotice({ ports: [], skipped: [] })).toEqual([]);
});
});
+4 -1
View File
@@ -1003,7 +1003,10 @@ export async function runLocalEgressAsync({
case 'disconnected':
if (connected) {
connected = false;
onDisconnected?.(line);
// A disconnect we caused by stopping is not something to warn about.
if (!signal.aborted) {
onDisconnected?.(line);
}
}
break;
case 'other':
+24 -34
View File
@@ -149,23 +149,18 @@ export function getLoopbackForwardPlan(
return { ports: [...ports].sort((a, b) => a - b), skipped };
}
export function formatLoopbackForwardNotice({ ports, skipped }: LoopbackForwardPlan): string[] {
const lines: string[] = [];
if (ports.length > 0) {
const addresses = ports.map(port => `127.0.0.1:${port}`).join(', ');
lines.push(
`${addresses} in the simulator ${ports.length === 1 ? 'reaches' : 'reach'} the same ` +
`${ports.length === 1 ? 'port' : 'ports'} on this machine (like adb reverse), so dev server ` +
'URLs that use 127.0.0.1 work.'
);
/**
* Only the exception is worth a line: forwarded ports just work, but an entry
* the device host will not forward changes what the reader would expect.
*/
export function formatLoopbackForwardNotice({ skipped }: LoopbackForwardPlan): string[] {
if (skipped.length === 0) {
return [];
}
if (skipped.length > 0) {
lines.push(
`${skipped.join(', ')} ${skipped.length === 1 ? 'is' : 'are'} reachable by name only: ` +
'privileged ports and the egress proxy port are not forwarded to 127.0.0.1 in the simulator.'
);
}
return lines;
return [
`${skipped.join(', ')} ${skipped.length === 1 ? 'is' : 'are'} reachable by name only: ` +
'privileged ports and the egress proxy port are not forwarded to 127.0.0.1 in the simulator.',
];
}
export function getLocalEgressEnvironmentVariables(
@@ -273,35 +268,30 @@ export function formatRemoteSessionInstructions(
}
const egressCommand =
configType === 'env' ? 'eas simulator:egress --config-type env' : 'eas simulator:egress';
const summary =
"🔀 Local egress: the simulator's HTTP(S) traffic exits from this machine." +
(egress.allow.length > 0 ? ` It can also reach ${egress.allow.join(', ')}.` : '');
return [
instructions,
'',
'🔀 This session can route proxied HTTP(S) requests through this machine.',
...(configType === 'env'
? Object.entries(getLocalEgressEnvironmentVariables(egress)).map(
([key, value]) => `export ${key}='${value}'`
)
: []),
summary,
...formatLoopbackForwardNotice(getLoopbackForwardPlan(egress.allow, egress.port)),
// In interactive mode the client starts in this terminal once the session is
// ready and stops with it, so there is nothing for the reader to run.
...(egressClientRunsInline
? [
'The egress client runs in this terminal for the life of the session. If this process exits, reconnect the tunnel from another shell with:',
'',
egressCommand,
]
? []
: [
...(configType === 'env'
? Object.entries(getLocalEgressEnvironmentVariables(egress)).map(
([key, value]) => `export ${key}='${value}'`
)
: []),
'Run the egress client to connect the tunnel:',
'',
egressCommand,
'',
'Keep it running for the life of the session.',
]),
...(egress.allow.length > 0
? [
'',
`The simulator may reach ${egress.allow.join(', ')} on this machine's network.`,
...formatLoopbackForwardNotice(getLoopbackForwardPlan(egress.allow, egress.port)),
]
: []),
].join('\n');
}