mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-14 17:50:32 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc37c1c360 | ||
|
|
e78771e0f4 | ||
|
|
288bc44dfc | ||
|
|
d3db3926e3 | ||
|
|
5b87ba0adb | ||
|
|
0b2cea7bb7 | ||
|
|
eab4fc3cd5 | ||
|
|
e2e8cd245c | ||
|
|
80d46f0783 | ||
|
|
3b6b63a273 |
No files matched your search
+16
-2
@@ -8,6 +8,22 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- [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
|
||||
|
||||
- [eas-cli] Add `--egress-allow <host:port>` to `eas simulator:start` so local egress sessions can reach dev servers on this machine or its network. ([#4369](https://github.com/expo/eas-cli/pull/4369) by [@brentvatne](https://github.com/brentvatne))
|
||||
- [eas-cli][build-tools] Forward `--egress-allow localhost:<port>` and `127.0.0.1:<port>` entries from the simulator host's loopback interface to this machine (like `adb reverse`), so dev server URLs that use `127.0.0.1` work through local egress. ([#4369](https://github.com/expo/eas-cli/pull/4369) by [@brentvatne](https://github.com/brentvatne))
|
||||
- [build-tools] Add the `eas/start_local_egress` step for iOS simulator sessions with local egress: the device host's system proxy points at a reverse tunnel to the EAS CLI machine, so HTTP(S) requests that honor it exit from that machine and third parties see its IP. ([#4364](https://github.com/expo/eas-cli/pull/4364) by [@brentvatne](https://github.com/brentvatne))
|
||||
@@ -18,8 +34,6 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
- [build-tools] Report a system error when project source refresh fails for a remote build. ([#4359](https://github.com/expo/eas-cli/pull/4359) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
## [24.0.0](https://github.com/expo/eas-cli/releases/tag/v24.0.0) - 2026-09-09
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"STAGING": "24.0.0",
|
||||
"PRODUCTION": "24.0.0"
|
||||
"STAGING": "24.1.1",
|
||||
"PRODUCTION": "24.1.1"
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"version": "24.1.0",
|
||||
"version": "24.1.2",
|
||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
|
||||
}
|
||||
+131
-131
File diff suppressed because it is too large.
Load diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eas-cli",
|
||||
"version": "24.1.0",
|
||||
"version": "24.1.2",
|
||||
"description": "EAS command line tool",
|
||||
"keywords": [
|
||||
"cli",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Config } from '@oclif/core';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { AnalyticsWithOrchestration } from '../../analytics/AnalyticsManager';
|
||||
@@ -24,17 +25,24 @@ jest.mock('../../analytics/AnalyticsManager', () => {
|
||||
});
|
||||
jest.mock('../../log');
|
||||
|
||||
let originalProcessArgv: string[];
|
||||
const mockRequestId = uuidv4();
|
||||
|
||||
beforeAll(() => {
|
||||
originalProcessArgv = process.argv;
|
||||
process.argv = [];
|
||||
});
|
||||
/**
|
||||
* A Config that is never loaded. Letting oclif load one discovers and requires
|
||||
* every command in the package, which takes seconds under coverage with a cold
|
||||
* transform cache and has timed out on slow CI runners. Nothing here needs the
|
||||
* command table or hooks.
|
||||
*/
|
||||
function getMockOclifConfig(): Config {
|
||||
const config = new Config({ root: __dirname });
|
||||
config.runHook = async () => ({
|
||||
failures: [],
|
||||
successes: [],
|
||||
});
|
||||
return config;
|
||||
}
|
||||
|
||||
afterAll(() => {
|
||||
process.argv = originalProcessArgv;
|
||||
});
|
||||
let mockConfig: Config;
|
||||
|
||||
const analytics: AnalyticsWithOrchestration = {
|
||||
logEvent: jest.fn((): void => {}),
|
||||
@@ -44,42 +52,57 @@ const analytics: AnalyticsWithOrchestration = {
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
mockConfig = getMockOclifConfig();
|
||||
});
|
||||
|
||||
/**
|
||||
* The config oclif handed to the most recently run command. Command.run returns
|
||||
* a pre-built Config untouched only when it was created by the same @oclif/core
|
||||
* copy the command extends; otherwise it silently builds and fully loads a new
|
||||
* one, bringing back the command discovery this suite avoids.
|
||||
*/
|
||||
let lastCommandConfig: Config | undefined;
|
||||
|
||||
const createTestEasCommand = (): any => {
|
||||
const { createAnalyticsAsync } = jest.requireMock('../../analytics/AnalyticsManager');
|
||||
createAnalyticsAsync.mockResolvedValue(analytics);
|
||||
const EasCommand = require('../EasCommand').default;
|
||||
|
||||
class TestEasCommand extends EasCommand {
|
||||
async runAsync(): Promise<void> {}
|
||||
async runAsync(): Promise<void> {
|
||||
lastCommandConfig = this.config;
|
||||
}
|
||||
}
|
||||
|
||||
TestEasCommand.id = 'testEasCommand'; // normally oclif will assign ids, but b/c this is located outside the commands folder it will not
|
||||
return TestEasCommand;
|
||||
};
|
||||
|
||||
// These tests go through the static Command.run rather than constructing an
|
||||
// instance and calling run() like other command tests, because they cover
|
||||
// oclif's catch and finally wiring, which only runs inside the static path.
|
||||
describe('EasCommand', () => {
|
||||
describe('without exceptions', () => {
|
||||
// The first test in this suite should have an increased timeout
|
||||
// because of the implementation of Command from @oclif/command.
|
||||
// It seems that loading config takes significant amount of time
|
||||
// and I'm not sure how to mock it.
|
||||
//
|
||||
// See https://github.com/oclif/command/blob/master/src/command.ts#L80
|
||||
// and look for "Config.load"
|
||||
it('ensures the user data is read', async () => {
|
||||
const TestEasCommand = createTestEasCommand();
|
||||
await TestEasCommand.run();
|
||||
await TestEasCommand.run([], mockConfig);
|
||||
|
||||
const SessionManager = jest.requireMock('../../user/SessionManager').default;
|
||||
const sessionManagerSpy = jest.spyOn(SessionManager.prototype, 'getUserAsync');
|
||||
expect(sessionManagerSpy).toBeCalledTimes(1);
|
||||
}, 60_000);
|
||||
});
|
||||
|
||||
it('runs with the unloaded config instead of loading one', async () => {
|
||||
const TestEasCommand = createTestEasCommand();
|
||||
await TestEasCommand.run([], mockConfig);
|
||||
|
||||
expect(lastCommandConfig).toBe(mockConfig);
|
||||
expect(mockConfig.plugins.size).toBe(0);
|
||||
});
|
||||
|
||||
it('initializes analytics', async () => {
|
||||
const TestEasCommand = createTestEasCommand();
|
||||
await TestEasCommand.run();
|
||||
await TestEasCommand.run([], mockConfig);
|
||||
|
||||
const { createAnalyticsAsync } = jest.requireMock('../../analytics/AnalyticsManager');
|
||||
expect(createAnalyticsAsync).toHaveBeenCalled();
|
||||
@@ -87,14 +110,14 @@ describe('EasCommand', () => {
|
||||
|
||||
it('flushes analytics', async () => {
|
||||
const TestEasCommand = createTestEasCommand();
|
||||
await TestEasCommand.run();
|
||||
await TestEasCommand.run([], mockConfig);
|
||||
|
||||
expect(analytics.flushAsync).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('flushes Sentry', async () => {
|
||||
const TestEasCommand = createTestEasCommand();
|
||||
await TestEasCommand.run();
|
||||
await TestEasCommand.run([], mockConfig);
|
||||
|
||||
const Sentry = jest.requireMock('../../sentry').default;
|
||||
expect(Sentry.flush).toHaveBeenCalled();
|
||||
@@ -102,7 +125,7 @@ describe('EasCommand', () => {
|
||||
|
||||
it('logs events', async () => {
|
||||
const TestEasCommand = createTestEasCommand();
|
||||
await TestEasCommand.run();
|
||||
await TestEasCommand.run([], mockConfig);
|
||||
|
||||
expect(analytics.logEvent).toHaveBeenCalledWith('action', {
|
||||
action: `eas ${TestEasCommand.id}`,
|
||||
@@ -114,7 +137,7 @@ describe('EasCommand', () => {
|
||||
it('flushes analytics', async () => {
|
||||
const TestEasCommand = createTestEasCommand();
|
||||
try {
|
||||
await TestEasCommand.run().then(() => {
|
||||
await TestEasCommand.run([], mockConfig).then(() => {
|
||||
throw new Error('foo');
|
||||
});
|
||||
} catch {}
|
||||
@@ -140,7 +163,7 @@ describe('EasCommand', () => {
|
||||
});
|
||||
|
||||
try {
|
||||
await TestEasCommand.run();
|
||||
await TestEasCommand.run([], mockConfig);
|
||||
} catch {}
|
||||
|
||||
expect(sentryScope.setTag).toHaveBeenCalledWith('command', TestEasCommand.id);
|
||||
@@ -167,7 +190,7 @@ describe('EasCommand', () => {
|
||||
});
|
||||
|
||||
try {
|
||||
await TestEasCommand.run();
|
||||
await TestEasCommand.run([], mockConfig);
|
||||
} catch {}
|
||||
|
||||
expect(Sentry.captureException).toHaveBeenCalledTimes(1);
|
||||
@@ -184,7 +207,7 @@ describe('EasCommand', () => {
|
||||
throw error;
|
||||
});
|
||||
try {
|
||||
await TestEasCommand.run();
|
||||
await TestEasCommand.run([], mockConfig);
|
||||
} catch {}
|
||||
|
||||
expect(logErrorSpy).toBeCalledWith('Unexpected, internal error message');
|
||||
@@ -204,7 +227,7 @@ describe('EasCommand', () => {
|
||||
throw error;
|
||||
});
|
||||
try {
|
||||
await TestEasCommand.run();
|
||||
await TestEasCommand.run([], mockConfig);
|
||||
} catch {}
|
||||
|
||||
expect(logErrorSpy).toBeCalledWith('Unexpected GraphQL error message');
|
||||
@@ -237,7 +260,7 @@ describe('EasCommand', () => {
|
||||
throw error;
|
||||
});
|
||||
try {
|
||||
await TestEasCommand.run();
|
||||
await TestEasCommand.run([], mockConfig);
|
||||
} catch {}
|
||||
|
||||
expect(logErrorSpy).toBeCalledWith(
|
||||
@@ -273,7 +296,7 @@ describe('EasCommand', () => {
|
||||
throw error;
|
||||
});
|
||||
try {
|
||||
await TestEasCommand.run();
|
||||
await TestEasCommand.run([], mockConfig);
|
||||
} catch {}
|
||||
|
||||
expect(logErrorSpy).toBeCalledWith(
|
||||
@@ -294,7 +317,7 @@ describe('EasCommand', () => {
|
||||
throw new Error('Error message');
|
||||
});
|
||||
try {
|
||||
await TestEasCommand.run();
|
||||
await TestEasCommand.run([], mockConfig);
|
||||
} catch (caughtError) {
|
||||
expect(caughtError).toBeInstanceOf(Error);
|
||||
expect((caughtError as Error).message).toEqual('testEasCommand command failed.');
|
||||
@@ -310,7 +333,7 @@ describe('EasCommand', () => {
|
||||
throw new CombinedError({ graphQLErrors });
|
||||
});
|
||||
try {
|
||||
await TestEasCommand.run();
|
||||
await TestEasCommand.run([], mockConfig);
|
||||
} catch (caughtError) {
|
||||
expect(caughtError).toBeInstanceOf(Error);
|
||||
expect((caughtError as Error).message).toEqual('GraphQL request failed.');
|
||||
@@ -340,7 +363,7 @@ describe('EasCommand', () => {
|
||||
throw new CombinedError({ graphQLErrors });
|
||||
});
|
||||
try {
|
||||
await TestEasCommand.run();
|
||||
await TestEasCommand.run([], mockConfig);
|
||||
} catch (caughtError) {
|
||||
expect(caughtError).toBeInstanceOf(Error);
|
||||
expect((caughtError as Error).message).toEqual('GraphQL request failed.');
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -419,7 +419,10 @@ export default class Simulator extends EasCommand {
|
||||
|
||||
Log.newLine();
|
||||
Log.log(
|
||||
formatRemoteSessionInstructions(remoteConfig, flags['out-config-type'], { egressAllow })
|
||||
formatRemoteSessionInstructions(remoteConfig, flags['out-config-type'], {
|
||||
egressAllow,
|
||||
egressClientRunsInline: !nonInteractive,
|
||||
})
|
||||
);
|
||||
Log.newLine();
|
||||
|
||||
@@ -427,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}`
|
||||
);
|
||||
@@ -444,11 +443,6 @@ export default class Simulator extends EasCommand {
|
||||
let egressError: Error | undefined;
|
||||
const egressAbortController = new AbortController();
|
||||
if (localEgress) {
|
||||
Log.log(
|
||||
"🔀 Running the egress client in this terminal. When connected, proxied HTTP(S) requests can use this machine's network."
|
||||
);
|
||||
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,14 +61,37 @@ 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).not.toContain('may reach');
|
||||
expect(instructions).toContain('eas simulator:egress');
|
||||
expect(instructions).toContain('Keep it running for the life of the session.');
|
||||
expect(instructions).not.toContain('It can also reach');
|
||||
expect(formatRemoteSessionInstructions(agentDeviceConfigWithEgress, 'env')).toContain(
|
||||
"export EAS_SIMULATOR_EGRESS_ALLOW=''"
|
||||
);
|
||||
});
|
||||
|
||||
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', () => {
|
||||
const egressAllow = ['localhost:3000', '192.168.1.20:8080'];
|
||||
expect(getLocalEgressConfig(agentDeviceConfigWithEgress, egressAllow)?.allow).toEqual(
|
||||
@@ -84,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.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -122,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([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -81,6 +81,12 @@ export type LocalEgressConfig = {
|
||||
|
||||
export type LocalEgressOptions = {
|
||||
egressAllow?: readonly string[];
|
||||
/**
|
||||
* Whether the calling command runs the egress client itself in the current
|
||||
* terminal, as interactive `simulator:start` does. When false the reader must
|
||||
* start `eas simulator:egress` in another process.
|
||||
*/
|
||||
egressClientRunsInline?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -143,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(
|
||||
@@ -258,34 +259,39 @@ export function sanitizeRemoteConfigForJson(
|
||||
export function formatRemoteSessionInstructions(
|
||||
remoteConfig: DeviceRunSessionRemoteConfig,
|
||||
configType: RemoteSessionInstructionsConfigType,
|
||||
{ egressAllow }: LocalEgressOptions = {}
|
||||
{ egressAllow, egressClientRunsInline = false }: LocalEgressOptions = {}
|
||||
): string {
|
||||
const instructions = formatControllerInstructions(remoteConfig, configType);
|
||||
const egress = getLocalEgressConfig(remoteConfig, egressAllow);
|
||||
if (!egress) {
|
||||
return instructions;
|
||||
}
|
||||
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}'`
|
||||
)
|
||||
: []),
|
||||
'Run the egress client to connect the tunnel:',
|
||||
'',
|
||||
configType === 'env' ? 'eas simulator:egress --config-type env' : 'eas simulator:egress',
|
||||
'',
|
||||
'Keep it running for the life of the session.',
|
||||
...(egress.allow.length > 0
|
||||
? [
|
||||
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
|
||||
? []
|
||||
: [
|
||||
...(configType === 'env'
|
||||
? Object.entries(getLocalEgressEnvironmentVariables(egress)).map(
|
||||
([key, value]) => `export ${key}='${value}'`
|
||||
)
|
||||
: []),
|
||||
'Run the egress client to connect the tunnel:',
|
||||
'',
|
||||
`The simulator may reach ${egress.allow.join(', ')} on this machine's network.`,
|
||||
...formatLoopbackForwardNotice(getLoopbackForwardPlan(egress.allow, egress.port)),
|
||||
]
|
||||
: []),
|
||||
egressCommand,
|
||||
'',
|
||||
'Keep it running for the life of the session.',
|
||||
]),
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user