Skip to content

Commit 6644379

Browse files
committed
feat: recent devices sort
1 parent df95e03 commit 6644379

6 files changed

Lines changed: 95 additions & 29 deletions

File tree

packages/plugin-platform-apple/src/lib/commands/build/createBuild.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@ export const createBuild = async (
3131
normalizeArgs(args, xcodeProject);
3232

3333
const { scheme, mode } = args.interactive
34-
? await selectFromInteractiveMode(xcodeProject, args.scheme, args.mode)
34+
? await selectFromInteractiveMode(
35+
xcodeProject,
36+
sourceDir,
37+
args.scheme,
38+
args.mode
39+
)
3540
: await getConfiguration(
3641
xcodeProject,
3742
sourceDir,

packages/plugin-platform-apple/src/lib/commands/run/createRun.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { selectFromInteractiveMode } from '../../utils/selectFromInteractiveMode
2020
import { outro, spinner } from '@clack/prompts';
2121
import { runOnMac } from './runOnMac.js';
2222
import { runOnMacCatalyst } from './runOnMacCatalyst.js';
23+
import { cacheRecentDevice } from './recentDevices.js';
2324

2425
export const createRun = async (
2526
platformName: ApplePlatform,
@@ -40,7 +41,12 @@ export const createRun = async (
4041
normalizeArgs(args, projectRoot, xcodeProject);
4142

4243
const { scheme, mode } = args.interactive
43-
? await selectFromInteractiveMode(xcodeProject, args.scheme, args.mode)
44+
? await selectFromInteractiveMode(
45+
xcodeProject,
46+
sourceDir,
47+
args.scheme,
48+
args.mode
49+
)
4450
: await getConfiguration(
4551
xcodeProject,
4652
sourceDir,
@@ -75,9 +81,10 @@ export const createRun = async (
7581
);
7682
}
7783
loader.stop('Found available devices and simulators.');
78-
const device = await selectDevice(devices, args, platformName);
84+
const device = await selectDevice(devices, args, platformName, projectRoot);
7985

8086
if (device) {
87+
cacheRecentDevice(device, projectRoot, platformName);
8188
if (device.type === 'simulator') {
8289
await runOnSimulator(
8390
device,
@@ -108,8 +115,13 @@ export const createRun = async (
108115
if (bootedSimulators.length === 0) {
109116
// fallback to present all devices when no device is selected
110117
if (isInteractive()) {
111-
const simulator = await promptForDeviceSelection(devices);
118+
const simulator = await promptForDeviceSelection(
119+
devices,
120+
projectRoot,
121+
platformName
122+
);
112123
bootedSimulators.push(simulator);
124+
cacheRecentDevice(simulator, projectRoot, platformName);
113125
} else {
114126
logger.debug(
115127
'No booted devices or simulators found. Launching first available simulator...'
@@ -146,12 +158,13 @@ export const createRun = async (
146158
async function selectDevice(
147159
devices: Device[],
148160
args: RunFlags,
149-
platform: ApplePlatform
161+
platform: ApplePlatform,
162+
projectRoot: string
150163
) {
151164
const { simulator, udid, interactive } = args;
152165
let device;
153166
if (interactive) {
154-
device = await promptForDeviceSelection(devices);
167+
device = await promptForDeviceSelection(devices, projectRoot, platform);
155168
} else if (udid) {
156169
device = devices.find((d) => d.udid === udid);
157170
} else if (args.device) {
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import path from 'node:path';
2+
import fs from 'node:fs';
3+
import { ApplePlatform, Device } from '../../types/index.js';
4+
import { cacheManager, logger } from '@callstack/rnef-tools';
5+
6+
function getProjectNameFromPackageJson(projectRoot: string) {
7+
const packageJsonPath = path.join(projectRoot, 'package.json');
8+
return JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')).name;
9+
}
10+
11+
export function cacheRecentDevice(
12+
device: Device,
13+
projectRoot: string,
14+
platform: ApplePlatform
15+
) {
16+
const cacheKey = 'recentDevicesUDID-' + platform;
17+
const name = getProjectNameFromPackageJson(projectRoot);
18+
try {
19+
const recentDevices = getRecentDevices(projectRoot, platform);
20+
const newRecentDevices = [device.udid, ...recentDevices];
21+
const uniqueDevices = Array.from(new Set(newRecentDevices)).slice(0, 5);
22+
cacheManager.set(name, cacheKey, JSON.stringify(uniqueDevices));
23+
} catch (error) {
24+
logger.debug(
25+
`Failed to cache recent device ${device.name} with UDID ${device.udid}. ${error}`
26+
);
27+
}
28+
}
29+
30+
function getRecentDevices(
31+
projectRoot: string,
32+
platform: ApplePlatform
33+
): string[] {
34+
const cacheKey = 'recentDevicesUDID-' + platform;
35+
const name = getProjectNameFromPackageJson(projectRoot);
36+
const recentDevicesString = cacheManager.get(name, cacheKey);
37+
try {
38+
return recentDevicesString ? JSON.parse(recentDevicesString) : [];
39+
} catch (error) {
40+
logger.debug(`Failed to read recent devices from cache. ${error}`);
41+
return [];
42+
}
43+
}
44+
45+
export function sortByRecentDevices(
46+
devices: Device[],
47+
projectRoot: string,
48+
platform: ApplePlatform
49+
) {
50+
const recentDevices = getRecentDevices(projectRoot, platform);
51+
const udids = Array.from(
52+
new Set([...recentDevices, ...devices.map(({ udid }) => udid)])
53+
);
54+
return udids
55+
.map((udid) => devices.find((device) => device.udid === udid))
56+
.filter(Boolean) as Device[];
57+
}

packages/plugin-platform-apple/src/lib/utils/__tests__/selectFromInteractiveMode.test.ts

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ describe('selectFromInteractiveMode', () => {
4949
};
5050
const result = await selectFromInteractiveMode(
5151
xcodeInfo,
52+
'/path/to/TestApp/ios',
5253
'TestScheme',
5354
'Debug'
5455
);
@@ -75,6 +76,7 @@ describe('selectFromInteractiveMode', () => {
7576
};
7677
const result = await selectFromInteractiveMode(
7778
xcodeInfo,
79+
'/path/to/TestApp/ios',
7880
'TestScheme',
7981
'Debug'
8082
);
@@ -103,6 +105,7 @@ describe('selectFromInteractiveMode', () => {
103105
};
104106
const result = await selectFromInteractiveMode(
105107
xcodeInfo,
108+
'/path/to/TestApp/ios',
106109
'TestScheme',
107110
'Debug'
108111
);
@@ -130,6 +133,7 @@ describe('selectFromInteractiveMode', () => {
130133
};
131134
const result = await selectFromInteractiveMode(
132135
xcodeInfo,
136+
'/path/to/TestApp/ios',
133137
'TestScheme',
134138
'Debug'
135139
);
@@ -150,23 +154,4 @@ describe('selectFromInteractiveMode', () => {
150154
`Automatically selected ${bold('Debug')} configuration.`
151155
);
152156
});
153-
154-
it('should handle undefined scheme and mode', async () => {
155-
const result = await selectFromInteractiveMode({
156-
scheme: undefined,
157-
mode: undefined,
158-
info: {
159-
schemes: ['Scheme1', 'Scheme2'],
160-
configurations: ['Debug', 'Release'],
161-
name: 'TestApp',
162-
},
163-
});
164-
165-
expect(result).toEqual({
166-
scheme: undefined,
167-
mode: undefined,
168-
});
169-
expect(promptForSchemeSelection).not.toHaveBeenCalled();
170-
expect(promptForConfigurationSelection).not.toHaveBeenCalled();
171-
});
172157
});

packages/plugin-platform-apple/src/lib/utils/prompts.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { select } from '@clack/prompts';
22
import color from 'picocolors';
33
import { checkCancelPrompt } from '@callstack/rnef-tools';
4-
import { Device } from '../types/index.js';
4+
import { ApplePlatform, Device } from '../types/index.js';
5+
import { sortByRecentDevices } from '../commands/run/recentDevices.js';
56

67
export async function promptForSchemeSelection(schemes: string[]) {
78
return checkCancelPrompt<string>(
@@ -29,11 +30,16 @@ export async function promptForConfigurationSelection(
2930
);
3031
}
3132

32-
export async function promptForDeviceSelection(devices: Device[]) {
33+
export async function promptForDeviceSelection(
34+
devices: Device[],
35+
projectRoot: string,
36+
platformName: ApplePlatform
37+
) {
38+
const sortedDevices = sortByRecentDevices(devices, projectRoot, platformName);
3339
return checkCancelPrompt<Device>(
3440
await select({
3541
message: 'Select the device / simulator you want to use',
36-
options: devices.map((d) => {
42+
options: sortedDevices.map((d) => {
3743
const markDevice = d.type === 'device' ? ` - (physical device)` : '';
3844
return {
3945
label: `${d.name} ${color.dim(`(${d.version})${markDevice}`)}`,

packages/plugin-platform-apple/src/lib/utils/selectFromInteractiveMode.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ import { getInfo } from './getInfo.js';
99

1010
export async function selectFromInteractiveMode(
1111
xcodeProject: XcodeProjectInfo,
12+
sourceDir: string,
1213
scheme: string,
1314
mode: string
1415
): Promise<{ scheme: string; mode: string }> {
1516
let newScheme = scheme;
1617
let newMode = mode;
17-
const sourceDir = process.cwd();
1818
const info = await getInfo(xcodeProject, sourceDir);
1919

2020
const schemes = info?.schemes;

0 commit comments

Comments
 (0)