Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 33 additions & 6 deletions packages/stim-cli/src/__tests__/device-remote.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@ describe('explicit backend selection', () => {
test('proxy requires both daemon variables with a precise remedy', async () => {
const missingToken = await resolveRemoteContext({
root,
label: 'wt',
backend: 'proxy',
easBin: '/bin/eas',
env: { AGENT_DEVICE_DAEMON_BASE_URL: env.AGENT_DEVICE_DAEMON_BASE_URL },
Expand All @@ -142,7 +141,6 @@ describe('explicit backend selection', () => {

const missingUrl = await resolveRemoteContext({
root,
label: 'wt',
backend: 'proxy',
easBin: '/bin/eas',
env: { AGENT_DEVICE_DAEMON_AUTH_TOKEN: env.AGENT_DEVICE_DAEMON_AUTH_TOKEN },
Expand All @@ -158,7 +156,6 @@ describe('explicit backend selection', () => {
test('proxy uses only the supplied daemon and never creates an EAS session', async () => {
const resolved = await resolveRemoteContext({
root,
label: 'wt',
backend: 'proxy',
easBin: '/bin/eas',
env,
Expand All @@ -180,7 +177,6 @@ describe('explicit backend selection', () => {
test('eas requires the EAS CLI even when proxy variables are present', async () => {
const resolved = await resolveRemoteContext({
root,
label: 'wt',
backend: 'eas',
easBin: null,
env,
Expand All @@ -196,7 +192,6 @@ describe('explicit backend selection', () => {
test('eas ignores proxy variables and creates an EAS session', async () => {
const resolved = await resolveRemoteContext({
root,
label: 'wt',
backend: 'eas',
easBin: '/bin/eas',
env,
Expand Down Expand Up @@ -258,7 +253,6 @@ process.stdout.write(${JSON.stringify(CREATED)});

const resolved = await resolveRemoteContext({
root,
label: 'wt',
backend: 'eas',
easBin,
env: process.env,
Expand Down Expand Up @@ -287,6 +281,39 @@ process.stdout.write(${JSON.stringify(CREATED)});
);
});

describe('each workspace names its own remote session', () => {
async function bootNames(workspaceRoot: string) {
resetExecutor();
const resolved = await resolveRemoteContext({
root: workspaceRoot,
backend: 'eas',
easBin: '/bin/eas',
env: {},
lookupAgentDevice: () => '/bin/agent-device',
});
if (!('ctx' in resolved)) throw new Error(resolved.failed);
const exec = mockExec({ outputs: { sim: CREATED } });
await remoteIosDeps(resolved.ctx).ensureBooted({});
const sim = exec.calls.find((call) => call.file === '/bin/eas' && call.args[0] === 'sim');
return {
eas: sim?.args[sim.args.indexOf('--name') + 1],
agentDevice: JSON.parse(readFileSync(remoteProfilePath(workspaceRoot), 'utf-8')).session,
};
}

test('two worktrees whose app directories share a name do not share a session', async () => {
const first = join(root, 'a', 'app');
const second = join(root, 'b', 'app');
mkdirSync(first, { recursive: true });
mkdirSync(second, { recursive: true });
const one = await bootNames(first);
const two = await bootNames(second);
expect(one.agentDevice).not.toBe(two.agentDevice);
expect(one.eas).not.toBe(two.eas);
expect(one.eas).toMatch(/^stim-app-[0-9a-f]{16}$/);
});
});

describe('the expensive step happens after the Metro gate', () => {
test('ensureOwnedDevice creates no session and runs no command', async () => {
const exec = mockExec();
Expand Down
1 change: 0 additions & 1 deletion packages/stim-cli/src/commands/android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -832,7 +832,6 @@ export async function runAndroid(options: RunAndroidOptions = {} as RunAndroidOp
if (remoteBackend) {
const resolved = await resolveRemoteDeviceContext({
root,
label,
platform: PLATFORM,
backend: remoteBackend,
easBin: resolveEasBin(root)?.file ?? null,
Expand Down
2 changes: 0 additions & 2 deletions packages/stim-cli/src/commands/ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,13 +473,11 @@ async function runIos(
const registerProject = () => d.upsertProject(root, { bundleId: d.detectBundleId(root) ?? undefined, isExpo });
if (remoteBackend !== 'eas') registerProject();
const proj = d.getProject(root);
const label = d.projectShortcut(root, proj);

let remoteDevice: ReturnType<typeof d.remoteIosDeps> | null = null;
if (remoteBackend) {
const resolved = await d.resolveRemoteContext({
root,
label,
backend: remoteBackend,
easBin: d.resolveEasCliBin(root)?.file ?? null,
});
Expand Down
4 changes: 1 addition & 3 deletions packages/stim-cli/src/commands/ios/dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ import {
} from '../../engine/xcode.ts';
import { pidExists, resolveProjectMetro } from '../../metro.ts';
import { createNdjsonWriter } from '../../ndjson.ts';
import { detectBundleId, detectIsExpo, findProjectRoot, projectShortcut } from '../../workspace/project.ts';
import { detectBundleId, detectIsExpo, findProjectRoot } from '../../workspace/project.ts';
import { resolveCacheProviderConfig, resolveSettings } from '../../workspace/settings.ts';
import { writeWorkspaceLaunch } from '../../supervisor/state.ts';
import { readWorkspaceState, writeWorkspaceState } from '../../workspace/workspace-state.ts';
Expand All @@ -79,7 +79,6 @@ export interface IosDeps {
devClientScheme: typeof devClientScheme;
getProject: typeof getProject;
upsertProject: typeof upsertProject;
projectShortcut: typeof projectShortcut;
checkDeviceCapacity: typeof checkDeviceCapacity;
ensureOwnedDevice: typeof ensureOwnedDevice;
listIosRuntimes: typeof listIosRuntimes;
Expand Down Expand Up @@ -157,7 +156,6 @@ export const DEFAULT_DEPS: IosDeps = {
devClientScheme,
getProject,
upsertProject,
projectShortcut,
checkDeviceCapacity,
ensureOwnedDevice,
listIosRuntimes,
Expand Down
8 changes: 4 additions & 4 deletions packages/stim-cli/src/engine/device-remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { getExecutor } from '../exec.ts';
import { readJsonObject } from '../json-file.ts';
import { pidExists } from '../metro.ts';
import { gateMetroOrigin, REMOTE_METRO_WRONG } from './metro-gate.ts';
import { workspaceDir, workspaceLogsDir, workspaceStateFile } from '../workspace/paths.ts';
import { workspaceDir, workspaceId, workspaceLogsDir, workspaceStateFile } from '../workspace/paths.ts';
import { clearRemoteSession, readMetroTunnel, readRemoteSession } from '../supervisor/state.ts';
import { ownedDeviceLabel } from '../workspace/project.ts';
import {
acceptAlertArgs,
closeArgs,
Expand Down Expand Up @@ -424,7 +425,6 @@ function remoteDeviceDeps(ctx: RemoteContext) {

export async function resolveRemoteContext({
root,
label,
backend,
platform = 'ios',
easBin,
Expand All @@ -433,7 +433,6 @@ export async function resolveRemoteContext({
maxDurationMinutes = null,
}: {
root: string;
label: string;
backend: RemoteDeviceBackend;
platform?: 'ios' | 'android';
easBin: string | null;
Expand Down Expand Up @@ -492,7 +491,8 @@ export async function resolveRemoteContext({
return {
ctx: {
root,
label,
// agent-device stores remote connection state per session name under ~/.agent-device/remote-connections/.
label: `${ownedDeviceLabel(root)}-${workspaceId(root)}`,
backend,
platform,
easBin: easBin ?? '',
Expand Down
Loading