Skip to content

Commit 14de55c

Browse files
authored
fix: support configurable iOS runner bundle IDs (#146)
1 parent ed5dd7b commit 14de55c

8 files changed

Lines changed: 90 additions & 16 deletions

File tree

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,8 @@ Diagnostics files:
475475
- Physical iOS device capture is best-effort: dropped frames are expected and true 60 FPS is not guaranteed even with `--fps 60`.
476476
- Physical iOS device recording defaults to uncapped (max available) FPS.
477477
- Use `agent-device record start [path] --fps <n>` (1-120) to set an explicit FPS cap on physical iOS devices.
478-
- iOS device runs require valid signing/provisioning (Automatic Signing recommended). Optional overrides: `AGENT_DEVICE_IOS_TEAM_ID`, `AGENT_DEVICE_IOS_SIGNING_IDENTITY`, `AGENT_DEVICE_IOS_PROVISIONING_PROFILE`.
478+
- iOS device runs require valid signing/provisioning (Automatic Signing recommended). Optional overrides: `AGENT_DEVICE_IOS_TEAM_ID`, `AGENT_DEVICE_IOS_SIGNING_IDENTITY`, `AGENT_DEVICE_IOS_PROVISIONING_PROFILE`, `AGENT_DEVICE_IOS_BUNDLE_ID`.
479+
- Free Apple Developer (Personal Team) accounts may need a unique runner bundle id; set `AGENT_DEVICE_IOS_BUNDLE_ID` to a reverse-DNS identifier unique to your team (for example `com.yourname.agentdevice.runner`).
479480

480481
## Testing
481482

@@ -517,6 +518,7 @@ Environment selectors:
517518
- `AGENT_DEVICE_IOS_TEAM_ID=<team-id>` optional Team ID override for iOS device runner signing.
518519
- `AGENT_DEVICE_IOS_SIGNING_IDENTITY=<identity>` optional signing identity override.
519520
- `AGENT_DEVICE_IOS_PROVISIONING_PROFILE=<profile>` optional provisioning profile specifier for iOS device runner signing.
521+
- `AGENT_DEVICE_IOS_BUNDLE_ID=<reverse-dns-id>` optional iOS runner app bundle id base. Tests derive from this as `<id>.uitests`.
520522
- `AGENT_DEVICE_IOS_RUNNER_DERIVED_PATH=<path>` optional override for iOS runner derived data root. By default, simulator uses `~/.agent-device/ios-runner/derived` and physical device uses `~/.agent-device/ios-runner/derived/device`. If you set this override, use separate paths per kind to avoid simulator/device artifact collisions.
521523
- `AGENT_DEVICE_IOS_CLEAN_DERIVED=1` rebuild iOS runner artifacts from scratch for runtime daemon-triggered builds (`pnpm ad ...`) on the selected path. `pnpm build:xcuitest` (alias of `pnpm build:xcuitest:ios`), `pnpm build:xcuitest:tvos`, and `pnpm build:all` already clear their default derived paths and do not require this variable. When `AGENT_DEVICE_IOS_RUNNER_DERIVED_PATH` is set, cleanup is blocked by default; set `AGENT_DEVICE_IOS_ALLOW_OVERRIDE_DERIVED_CLEAN=1` only for trusted custom paths.
522524

ios-runner/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.pbxproj

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,8 @@
204204
isa = XCBuildConfiguration;
205205
buildSettings = {
206206
ALWAYS_SEARCH_USER_PATHS = NO;
207+
AGENT_DEVICE_IOS_RUNNER_APP_BUNDLE_ID = com.callstack.agentdevice.runner;
208+
AGENT_DEVICE_IOS_RUNNER_TEST_BUNDLE_ID = "$(AGENT_DEVICE_IOS_RUNNER_APP_BUNDLE_ID).uitests";
207209
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
208210
CLANG_ANALYZER_NONNULL = YES;
209211
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@@ -268,6 +270,8 @@
268270
isa = XCBuildConfiguration;
269271
buildSettings = {
270272
ALWAYS_SEARCH_USER_PATHS = NO;
273+
AGENT_DEVICE_IOS_RUNNER_APP_BUNDLE_ID = com.callstack.agentdevice.runner;
274+
AGENT_DEVICE_IOS_RUNNER_TEST_BUNDLE_ID = "$(AGENT_DEVICE_IOS_RUNNER_APP_BUNDLE_ID).uitests";
271275
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
272276
CLANG_ANALYZER_NONNULL = YES;
273277
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@@ -342,7 +346,7 @@
342346
"@executable_path/Frameworks",
343347
);
344348
MARKETING_VERSION = 1.0;
345-
PRODUCT_BUNDLE_IDENTIFIER = com.myapp.AgentDeviceRunner;
349+
PRODUCT_BUNDLE_IDENTIFIER = "$(AGENT_DEVICE_IOS_RUNNER_APP_BUNDLE_ID)";
346350
PRODUCT_NAME = "$(TARGET_NAME)";
347351
STRING_CATALOG_GENERATE_SYMBOLS = YES;
348352
SWIFT_APPROACHABLE_CONCURRENCY = YES;
@@ -377,7 +381,7 @@
377381
"@executable_path/Frameworks",
378382
);
379383
MARKETING_VERSION = 1.0;
380-
PRODUCT_BUNDLE_IDENTIFIER = com.myapp.AgentDeviceRunner;
384+
PRODUCT_BUNDLE_IDENTIFIER = "$(AGENT_DEVICE_IOS_RUNNER_APP_BUNDLE_ID)";
381385
PRODUCT_NAME = "$(TARGET_NAME)";
382386
STRING_CATALOG_GENERATE_SYMBOLS = YES;
383387
SWIFT_APPROACHABLE_CONCURRENCY = YES;
@@ -400,7 +404,7 @@
400404
GENERATE_INFOPLIST_FILE = YES;
401405
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
402406
MARKETING_VERSION = 1.0;
403-
PRODUCT_BUNDLE_IDENTIFIER = com.myapp.AgentDeviceRunnerUITests;
407+
PRODUCT_BUNDLE_IDENTIFIER = "$(AGENT_DEVICE_IOS_RUNNER_TEST_BUNDLE_ID)";
404408
PRODUCT_NAME = "$(TARGET_NAME)";
405409
STRING_CATALOG_GENERATE_SYMBOLS = NO;
406410
SWIFT_APPROACHABLE_CONCURRENCY = YES;
@@ -424,7 +428,7 @@
424428
GENERATE_INFOPLIST_FILE = YES;
425429
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
426430
MARKETING_VERSION = 1.0;
427-
PRODUCT_BUNDLE_IDENTIFIER = com.myapp.AgentDeviceRunnerUITests;
431+
PRODUCT_BUNDLE_IDENTIFIER = "$(AGENT_DEVICE_IOS_RUNNER_TEST_BUNDLE_ID)";
428432
PRODUCT_NAME = "$(TARGET_NAME)";
429433
STRING_CATALOG_GENERATE_SYMBOLS = NO;
430434
SWIFT_APPROACHABLE_CONCURRENCY = YES;

skills/agent-device/references/permissions.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ Use Automatic Signing in Xcode, or provide optional overrides:
1212
- `AGENT_DEVICE_IOS_TEAM_ID`
1313
- `AGENT_DEVICE_IOS_SIGNING_IDENTITY`
1414
- `AGENT_DEVICE_IOS_PROVISIONING_PROFILE`
15+
- `AGENT_DEVICE_IOS_BUNDLE_ID` (optional runner bundle-id base override)
16+
17+
Free Apple Developer (Personal Team) accounts may reject generic bundle IDs as unavailable.
18+
Set `AGENT_DEVICE_IOS_BUNDLE_ID` to a unique reverse-DNS identifier when that happens.
1519

1620
Security guidance for these overrides:
1721

@@ -23,7 +27,7 @@ Security guidance for these overrides:
2327

2428
If setup/build takes long, increase:
2529

26-
- `AGENT_DEVICE_DAEMON_TIMEOUT_MS` (default `45000`, for example `120000`)
30+
- `AGENT_DEVICE_DAEMON_TIMEOUT_MS` (default `90000`, for example `120000`)
2731

2832
If daemon startup fails with stale metadata hints, clean stale files and retry:
2933

src/daemon/handlers/__tests__/record-trace.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import path from 'node:path';
66
import { handleRecordTraceCommands } from '../record-trace.ts';
77
import { SessionStore } from '../../session-store.ts';
88
import type { SessionState } from '../../types.ts';
9+
import { IOS_RUNNER_CONTAINER_BUNDLE_IDS } from '../../../platforms/ios/runner-client.ts';
910

1011
type RecordTraceDeps = NonNullable<Parameters<typeof handleRecordTraceCommands>[0]['deps']>;
1112
type RunnerCall = {
@@ -160,7 +161,7 @@ test('record start/stop uses iOS runner on physical iOS devices', async () => {
160161
'--domain-type',
161162
'appDataContainer',
162163
'--domain-identifier',
163-
'com.myapp.AgentDeviceRunnerUITests.xctrunner',
164+
IOS_RUNNER_CONTAINER_BUNDLE_IDS[0] ?? '',
164165
]);
165166
assert.equal(sessionStore.get(sessionName)?.recording, undefined);
166167
});

src/platforms/ios/__tests__/runner-client.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
isRetryableRunnerError,
88
resolveRunnerEarlyExitHint,
99
resolveRunnerBuildDestination,
10+
resolveRunnerBundleBuildSettings,
1011
resolveRunnerDestination,
1112
resolveRunnerMaxConcurrentDestinationsFlag,
1213
resolveRunnerSigningBuildSettings,
@@ -124,6 +125,22 @@ test('resolveRunnerSigningBuildSettings applies optional overrides when provided
124125
]);
125126
});
126127

128+
test('resolveRunnerBundleBuildSettings returns default bundle identifiers', () => {
129+
assert.deepEqual(resolveRunnerBundleBuildSettings({}), [
130+
'AGENT_DEVICE_IOS_RUNNER_APP_BUNDLE_ID=com.callstack.agentdevice.runner',
131+
'AGENT_DEVICE_IOS_RUNNER_TEST_BUNDLE_ID=com.callstack.agentdevice.runner.uitests',
132+
]);
133+
});
134+
135+
test('resolveRunnerBundleBuildSettings uses AGENT_DEVICE_IOS_BUNDLE_ID when provided', () => {
136+
assert.deepEqual(resolveRunnerBundleBuildSettings({
137+
AGENT_DEVICE_IOS_BUNDLE_ID: 'com.example.agent-device.runner',
138+
}), [
139+
'AGENT_DEVICE_IOS_RUNNER_APP_BUNDLE_ID=com.example.agent-device.runner',
140+
'AGENT_DEVICE_IOS_RUNNER_TEST_BUNDLE_ID=com.example.agent-device.runner.uitests',
141+
]);
142+
});
143+
127144
test('assertSafeDerivedCleanup allows cleaning when no override is set', () => {
128145
assert.doesNotThrow(() => {
129146
assertSafeDerivedCleanup('/tmp/derived', {});

src/platforms/ios/runner-client.ts

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,39 @@ import { resolveTimeoutMs, resolveTimeoutSeconds } from '../../utils/timeouts.ts
1414
import { isRequestCanceled } from '../../daemon/request-cancel.ts';
1515
import { buildSimctlArgsForDevice } from './simctl.ts';
1616

17-
const iosRunnerContainerBundleIds = [
18-
process.env.AGENT_DEVICE_IOS_RUNNER_CONTAINER_BUNDLE_ID,
19-
process.env.AGENT_DEVICE_IOS_RUNNER_APP_BUNDLE_ID,
20-
'com.myapp.AgentDeviceRunnerUITests.xctrunner',
21-
'com.myapp.AgentDeviceRunner',
22-
]
23-
.map((id) => id?.trim() ?? '')
24-
.filter((id) => id.length > 0);
17+
const DEFAULT_IOS_RUNNER_APP_BUNDLE_ID = 'com.callstack.agentdevice.runner';
2518

26-
export const IOS_RUNNER_CONTAINER_BUNDLE_IDS: string[] = Array.from(new Set(iosRunnerContainerBundleIds));
19+
function normalizeBundleId(value: string | undefined): string {
20+
return value?.trim() ?? '';
21+
}
22+
23+
function resolveRunnerAppBundleId(env: NodeJS.ProcessEnv = process.env): string {
24+
const configured = normalizeBundleId(env.AGENT_DEVICE_IOS_BUNDLE_ID)
25+
|| normalizeBundleId(env.AGENT_DEVICE_IOS_RUNNER_APP_BUNDLE_ID);
26+
return configured || DEFAULT_IOS_RUNNER_APP_BUNDLE_ID;
27+
}
28+
29+
function resolveRunnerTestBundleId(env: NodeJS.ProcessEnv = process.env): string {
30+
const configured = normalizeBundleId(env.AGENT_DEVICE_IOS_RUNNER_TEST_BUNDLE_ID);
31+
if (configured) {
32+
return configured;
33+
}
34+
return `${resolveRunnerAppBundleId(env)}.uitests`;
35+
}
36+
37+
function resolveRunnerContainerBundleIds(env: NodeJS.ProcessEnv = process.env): string[] {
38+
const appBundleId = resolveRunnerAppBundleId(env);
39+
const testBundleId = resolveRunnerTestBundleId(env);
40+
return Array.from(new Set([
41+
normalizeBundleId(env.AGENT_DEVICE_IOS_RUNNER_CONTAINER_BUNDLE_ID),
42+
`${testBundleId}.xctrunner`,
43+
appBundleId,
44+
'com.myapp.AgentDeviceRunnerUITests.xctrunner',
45+
'com.myapp.AgentDeviceRunner',
46+
].filter((id) => id.length > 0)));
47+
}
48+
49+
export const IOS_RUNNER_CONTAINER_BUNDLE_IDS: string[] = resolveRunnerContainerBundleIds(process.env);
2750

2851
type RunnerCommand = {
2952
command:
@@ -445,6 +468,7 @@ async function ensureXctestrun(
445468
throw new AppError('COMMAND_FAILED', 'iOS runner project not found', { projectPath });
446469
}
447470

471+
const runnerBundleBuildSettings = resolveRunnerBundleBuildSettings(process.env);
448472
const signingBuildSettings = resolveRunnerSigningBuildSettings(process.env, device.kind === 'device');
449473
const provisioningArgs = device.kind === 'device' ? ['-allowProvisioningUpdates'] : [];
450474
try {
@@ -464,6 +488,7 @@ async function ensureXctestrun(
464488
resolveRunnerBuildDestination(device),
465489
'-derivedDataPath',
466490
derived,
491+
...runnerBundleBuildSettings,
467492
...provisioningArgs,
468493
...signingBuildSettings,
469494
],
@@ -591,9 +616,26 @@ export function resolveRunnerSigningBuildSettings(
591616
return args;
592617
}
593618

619+
export function resolveRunnerBundleBuildSettings(
620+
env: NodeJS.ProcessEnv = process.env,
621+
): string[] {
622+
const appBundleId = resolveRunnerAppBundleId(env);
623+
const testBundleId = resolveRunnerTestBundleId(env);
624+
return [
625+
`AGENT_DEVICE_IOS_RUNNER_APP_BUNDLE_ID=${appBundleId}`,
626+
`AGENT_DEVICE_IOS_RUNNER_TEST_BUNDLE_ID=${testBundleId}`,
627+
];
628+
}
629+
594630
function resolveSigningFailureHint(error: AppError): string | undefined {
595631
const details = error.details ? JSON.stringify(error.details) : '';
596632
const combined = `${error.message}\n${details}`.toLowerCase();
633+
if (
634+
combined.includes('failed registering bundle identifier')
635+
|| (combined.includes('app identifier') && combined.includes('not available'))
636+
) {
637+
return 'Set AGENT_DEVICE_IOS_BUNDLE_ID to a unique reverse-DNS value (for example, com.yourname.agentdevice.runner), then retry.';
638+
}
597639
if (combined.includes('requires a development team')) {
598640
return 'Configure signing in Xcode or set AGENT_DEVICE_IOS_TEAM_ID for physical-device runs.';
599641
}

website/docs/docs/commands.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,8 @@ tail -50 ~/.agent-device/sessions/default/app.log
355355
- `AGENT_DEVICE_IOS_TEAM_ID`
356356
- `AGENT_DEVICE_IOS_SIGNING_IDENTITY` (optional)
357357
- `AGENT_DEVICE_IOS_PROVISIONING_PROFILE`
358+
- `AGENT_DEVICE_IOS_BUNDLE_ID` (runner bundle-id base; tests use `<id>.uitests`)
359+
- Free Apple Developer (Personal Team) accounts can fail on unavailable generic bundle IDs; set `AGENT_DEVICE_IOS_BUNDLE_ID` to a unique reverse-DNS value.
358360
- If first-run XCTest setup/build is slow, increase daemon request timeout:
359361
- `AGENT_DEVICE_DAEMON_TIMEOUT_MS=120000` (default is `90000`)
360362
- For daemon startup troubleshooting:

website/docs/docs/installation.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ npx agent-device open Settings --platform ios
3030
- `AGENT_DEVICE_IOS_TEAM_ID`
3131
- `AGENT_DEVICE_IOS_SIGNING_IDENTITY`
3232
- `AGENT_DEVICE_IOS_PROVISIONING_PROFILE`
33+
- `AGENT_DEVICE_IOS_BUNDLE_ID` (optional runner bundle-id base override)
34+
- Free Apple Developer (Personal Team) accounts can fail with "bundle identifier is not available" for generic IDs; set `AGENT_DEVICE_IOS_BUNDLE_ID` to a unique reverse-DNS value (for example `com.yourname.agentdevice.runner`).
3335
- If device setup is slow, increase daemon timeout:
3436
- `AGENT_DEVICE_DAEMON_TIMEOUT_MS=120000` (default is `90000`)
3537
- If daemon startup reports stale metadata, remove stale files and retry:

0 commit comments

Comments
 (0)