Skip to content
Open
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ This is the log of notable changes to EAS CLI and related packages.

### 🐛 Bug fixes

- [eas-cli] Stop `eas build` from looping forever printing "There are still no registered devices." when a device registration method other than Website registers no devices. ([#4254](https://github.com/expo/eas-cli/pull/4254) by [@dennytosp](https://github.com/dennytosp))

### 🧹 Chores

## [22.2.0](https://github.com/expo/eas-cli/releases/tag/v22.2.0) - 2026-08-20
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,15 +388,24 @@ export class SetUpAdhocProvisioningProfile {
const devices = await ctx.ios.getDevicesForAppleTeamAsync(ctx.graphqlClient, app, appleTeam, {
useCache: false,
});
if (devices.length === 0) {
Log.warn('There are still no registered devices.');
// if the user used the input method there should be some devices available
if (method === RegistrationMethod.INPUT) {
throw new Error('Input registration method has failed');
}
} else {
if (devices.length > 0) {
return devices;
}

Log.warn('There are still no registered devices.');
// if the user used the input method there should be some devices available
if (method === RegistrationMethod.INPUT) {
throw new Error('Input registration method has failed');
}
// Only the website method registers devices out of band, so only there does waiting and
// reading the list again stand a chance of returning something. Every other method is done
// registering by the time it resolves - with nothing to wait for, looping would just reread
// the same empty list forever and leave the user no way out but killing the process.
if (method !== RegistrationMethod.WEBSITE) {
throw new Error(
`No devices were registered. Run 'eas device:create' to register your devices first.`
);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@ import { EasJson } from '@expo/eas-json';

import { Analytics } from '../../../../analytics/AnalyticsManager';
import { ExpoGraphqlClient } from '../../../../commandUtils/context/contextUtils/createGraphqlClient';
import DeviceCreateAction, { RegistrationMethod } from '../../../../devices/actions/create/action';
import {
Account,
AppleAppIdentifierFragment,
AppleDevice,
AppleDeviceClass,
AppleDeviceFragment,
AppleDistributionCertificateFragment,
AppleTeamFragment,
IosAppBuildCredentialsFragment,
} from '../../../../graphql/generated';
import Log from '../../../../log';
import { getApplePlatformFromTarget } from '../../../../project/ios/target';
import { selectAsync } from '../../../../prompts';
import { pressAnyKeyToContinueAsync, selectAsync } from '../../../../prompts';
import { Actor } from '../../../../user/User';
import { Client } from '../../../../vcs/vcs';
import { CredentialsContext, CredentialsContextProjectInfo } from '../../../context';
Expand Down Expand Up @@ -56,6 +58,11 @@ jest.mock('../SetUpDistributionCertificate', () => ({
import { SetUpDistributionCertificate } from '../SetUpDistributionCertificate';
jest.mock('../../../../project/ios/target');
jest.mock('../../../../prompts');
jest.mock('../../../../devices/actions/create/action', () => ({
__esModule: true,
...jest.requireActual('../../../../devices/actions/create/action'),
default: jest.fn(),
}));

describe(doUDIDsMatch, () => {
it('return false if UDIDs do not match', () => {
Expand Down Expand Up @@ -228,6 +235,73 @@ describe('runAsync', () => {
});
});

describe('registerDevicesAsync', () => {
beforeEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});

const setUpAdhocProvisioningProfile = new SetUpAdhocProvisioningProfile({
app: { account: {} as Account, projectName: 'projName', bundleIdentifier: 'bundleId' },
target: { targetName: 'targetName', bundleIdentifier: 'bundleId', entitlements: {} },
});

async function registerDevicesAsync(
ctx: CredentialsContext,
method: RegistrationMethod
): Promise<AppleDeviceFragment[]> {
jest
.mocked(DeviceCreateAction)
.mockImplementation(() => ({ runAsync: jest.fn().mockResolvedValue(method) }) as any);
return await (setUpAdhocProvisioningProfile as any).registerDevicesAsync(
ctx,
{} as AppleTeamFragment
);
}

it('returns the devices registered by the chosen method', async () => {
const { ctx } = setUpTest();

await expect(registerDevicesAsync(ctx, RegistrationMethod.DEVELOPER_PORTAL)).resolves.toEqual([
{ identifier: 'id1' },
{ identifier: 'id2' },
{ identifier: 'id3' },
]);
});

it('gives up when the developer portal method registers nothing', async () => {
const { ctx } = setUpTest();
ctx.ios.getDevicesForAppleTeamAsync = jest.fn().mockResolvedValue([]);

await expect(registerDevicesAsync(ctx, RegistrationMethod.DEVELOPER_PORTAL)).rejects.toThrow(
`No devices were registered. Run 'eas device:create' to register your devices first.`
);
expect(ctx.ios.getDevicesForAppleTeamAsync).toHaveBeenCalledTimes(1);
});

it('reports the input method failing on its own', async () => {
const { ctx } = setUpTest();
ctx.ios.getDevicesForAppleTeamAsync = jest.fn().mockResolvedValue([]);

await expect(registerDevicesAsync(ctx, RegistrationMethod.INPUT)).rejects.toThrow(
'Input registration method has failed'
);
});

it('waits for the website method until the devices show up', async () => {
const { ctx } = setUpTest();
ctx.ios.getDevicesForAppleTeamAsync = jest
.fn()
.mockResolvedValueOnce([])
.mockResolvedValue([{ identifier: 'id1' }] as AppleDeviceFragment[]);

await expect(registerDevicesAsync(ctx, RegistrationMethod.WEBSITE)).resolves.toEqual([
{ identifier: 'id1' },
]);
expect(pressAnyKeyToContinueAsync).toHaveBeenCalledTimes(2);
});
});

describe('refresh ad-hoc provisioning profile', () => {
beforeEach(() => {
jest.clearAllMocks();
Expand Down
Loading