Skip to content

Commit 8180d73

Browse files
authored
fix: launch iOS app on device (#525)
* fix: launch iOS app on device * changeset
1 parent e491d8c commit 8180d73

File tree

11 files changed

+121
-65
lines changed

11 files changed

+121
-65
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@rock-js/platform-apple-helpers': patch
3+
'create-rock': patch
4+
'@rock-js/tools': patch
5+
'rock': patch
6+
'rock-docs': patch
7+
---
8+
9+
fix: launch iOS app on device

packages/cli/src/lib/cli.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ import { createRequire } from 'node:module';
22
import { dirname } from 'node:path';
33
import { fileURLToPath } from 'node:url';
44
import { type CommandType, getConfig } from '@rock-js/config';
5-
import { colorLink, logger, resolveFilenameUp, RockError } from '@rock-js/tools';
5+
import {
6+
colorLink,
7+
logger,
8+
resolveFilenameUp,
9+
RockError,
10+
} from '@rock-js/tools';
611
import { Command } from 'commander';
712
import { checkDeprecatedOptions } from './checkDeprecatedOptions.js';
813
import { cleanPlugin } from './plugins/clean.js';

packages/cli/src/lib/plugins/remoteCache.ts

Lines changed: 13 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ import {
77
color,
88
colorLink,
99
formatArtifactName,
10+
getInfoPlist,
1011
getLocalArtifactPath,
1112
getLocalBinaryPath,
1213
handleDownloadResponse,
1314
handleUploadResponse,
1415
logger,
1516
relativeToCwd,
1617
RockError,
17-
spawn,
1818
spinner,
1919
} from '@rock-js/tools';
2020
import AdmZip from 'adm-zip';
@@ -195,7 +195,8 @@ ${output
195195

196196
// Upload index.html and manifest.plist for ad-hoc distribution
197197
if (args.adHoc) {
198-
const { version, bundleIdentifier } = await getInfoPlist(binaryPath);
198+
const { version, bundleIdentifier } =
199+
await getInfoPlistFromIpa(binaryPath);
199200
const { url: urlIndexHtml, getResponse: getResponseIndexHtml } =
200201
await remoteBuildCache.upload({
201202
artifactName,
@@ -279,7 +280,7 @@ ${deletedArtifacts
279280
return null;
280281
}
281282

282-
async function getInfoPlist(binaryPath: string) {
283+
async function getInfoPlistFromIpa(binaryPath: string) {
283284
const ipaFileName = path.basename(binaryPath);
284285
const appName = path.basename(ipaFileName, '.ipa');
285286
const ipaPath = binaryPath;
@@ -295,33 +296,15 @@ async function getInfoPlist(binaryPath: string) {
295296
const infoPlistBuffer = infoPlistEntry.getData();
296297
const tempPlistPath = path.join(os.tmpdir(), 'rock-temp-info.plist');
297298
fs.writeFileSync(tempPlistPath, infoPlistBuffer);
298-
299-
let version = 'unknown';
300-
let bundleIdentifier = 'unknown';
301-
try {
302-
await spawn('plutil', [
303-
'-convert',
304-
'json',
305-
'-o',
306-
tempPlistPath,
307-
tempPlistPath,
308-
]);
309-
310-
const jsonContent = fs.readFileSync(tempPlistPath, 'utf8');
311-
const infoPlistJson = JSON.parse(jsonContent) as Record<string, any>;
312-
313-
version =
314-
infoPlistJson['CFBundleShortVersionString'] ||
315-
infoPlistJson['CFBundleVersion'] ||
316-
'unknown';
317-
bundleIdentifier = infoPlistJson['CFBundleIdentifier'];
318-
} finally {
319-
if (fs.existsSync(tempPlistPath)) {
320-
fs.unlinkSync(tempPlistPath);
321-
}
322-
}
323-
324-
return { version, bundleIdentifier };
299+
const infoPlistJson = await getInfoPlist(tempPlistPath);
300+
fs.unlinkSync(tempPlistPath);
301+
return {
302+
version:
303+
infoPlistJson?.['CFBundleShortVersionString'] ||
304+
infoPlistJson?.['CFBundleVersion'] ||
305+
'unknown',
306+
bundleIdentifier: infoPlistJson?.['CFBundleIdentifier'] || 'unknown',
307+
};
325308
}
326309

327310
async function getBinaryBuffer(

packages/create-app/src/lib/utils/initInExistingProject.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ export async function initInExistingProject(projectRoot: string) {
2626
'@rock-js/platform-ios',
2727
];
2828

29-
loader.start(`Adding ${color.bold('Rock')} dependencies with ${color.bold(pkgManager)}`);
29+
loader.start(
30+
`Adding ${color.bold('Rock')} dependencies with ${color.bold(pkgManager)}`,
31+
);
3032
await addDevDependencies(projectRoot, pkgManager, rockPackages);
3133
loader.stop(`Added ${color.bold('Rock')} dependencies`);
3234

@@ -169,8 +171,7 @@ function updateAndroidBuildGradle(projectRoot: string, sourceDir: string) {
169171
if (!fs.existsSync(filePath)) {
170172
return;
171173
}
172-
const desired =
173-
'cliFile = file("../../node_modules/rock/dist/src/bin.js")';
174+
const desired = 'cliFile = file("../../node_modules/rock/dist/src/bin.js")';
174175
const content = fs.readFileSync(filePath, 'utf8');
175176
const replaced = content.replace(
176177
/\/\/\s+cliFile\s*=\s*file\([^)]*\)/g,

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

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ ${devices
147147
saveLocalBuildCache(artifactName, appPath);
148148
await runOnSimulator(device, appPath, infoPlistPath);
149149
} else if (device.type === 'device') {
150-
const { appPath } = await buildApp({
150+
const { appPath, bundleIdentifier } = await buildApp({
151151
args,
152152
projectConfig,
153153
platformName,
@@ -156,7 +156,12 @@ ${devices
156156
reactNativePath,
157157
binaryPath,
158158
});
159-
await runOnDevice(device, appPath, projectConfig.sourceDir);
159+
await runOnDevice(
160+
device,
161+
appPath,
162+
projectConfig.sourceDir,
163+
bundleIdentifier,
164+
);
160165
}
161166
return;
162167
} else {
@@ -189,23 +194,29 @@ ${devices
189194
}
190195
}
191196
for (const bootedDevice of bootedDevices) {
192-
const [, { appPath, infoPlistPath }] = await Promise.all([
193-
launchSimulator(bootedDevice),
194-
buildApp({
195-
args,
196-
projectConfig,
197-
platformName,
198-
udid: bootedDevice.udid,
199-
projectRoot,
200-
reactNativePath,
201-
binaryPath,
202-
}),
203-
]);
197+
const [, { appPath, infoPlistPath, bundleIdentifier }] =
198+
await Promise.all([
199+
launchSimulator(bootedDevice),
200+
buildApp({
201+
args,
202+
projectConfig,
203+
platformName,
204+
udid: bootedDevice.udid,
205+
projectRoot,
206+
reactNativePath,
207+
binaryPath,
208+
}),
209+
]);
204210
saveLocalBuildCache(artifactName, appPath);
205211
if (bootedDevice.type === 'simulator') {
206212
await runOnSimulator(bootedDevice, appPath, infoPlistPath);
207213
} else {
208-
await runOnDevice(bootedDevice, appPath, projectConfig.sourceDir);
214+
await runOnDevice(
215+
bootedDevice,
216+
appPath,
217+
projectConfig.sourceDir,
218+
bundleIdentifier,
219+
);
209220
}
210221
}
211222
}

packages/platform-apple-helpers/src/lib/commands/run/getBuildSettings.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ type BuildSettings = {
88
EXECUTABLE_FOLDER_PATH: string;
99
FULL_PRODUCT_NAME: string;
1010
WRAPPER_EXTENSION: string;
11+
PRODUCT_BUNDLE_IDENTIFIER: string;
1112
};
1213

1314
export async function getBuildSettings({
@@ -28,7 +29,11 @@ export async function getBuildSettings({
2829
scheme: string;
2930
target?: string;
3031
buildFolder?: string;
31-
}): Promise<{ appPath: string; infoPlistPath: string }> {
32+
}): Promise<{
33+
appPath: string;
34+
infoPlistPath: string;
35+
bundleIdentifier: string;
36+
}> {
3237
const destination = destinations[0];
3338
const sdk = destination.match(/simulator/i)
3439
? getSimulatorPlatformSDK(platformName)
@@ -102,6 +107,7 @@ export async function getBuildSettings({
102107
return {
103108
appPath,
104109
infoPlistPath: path.join(targetBuildDir, infoPlistPath),
110+
bundleIdentifier: buildSettings.PRODUCT_BUNDLE_IDENTIFIER,
105111
};
106112
}
107113

packages/platform-apple-helpers/src/lib/commands/run/runOnDevice.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,39 @@ export async function runOnDevice(
66
selectedDevice: Device,
77
binaryPath: string,
88
sourceDir: string,
9+
bundleIdentifier: string,
910
) {
10-
const deviceCtlArgs = [
11-
'devicectl',
12-
'device',
13-
'install',
14-
'app',
15-
'--device',
16-
selectedDevice.udid,
17-
binaryPath,
18-
];
1911
const loader = spinner();
2012
loader.start(
2113
`Installing and launching your app on ${color.bold(selectedDevice.name)}`,
2214
);
2315
try {
24-
await spawn('xcrun', deviceCtlArgs, { cwd: sourceDir });
16+
await spawn(
17+
'xcrun',
18+
[
19+
'devicectl',
20+
'device',
21+
'install',
22+
'app',
23+
'--device',
24+
selectedDevice.udid,
25+
binaryPath,
26+
],
27+
{ cwd: sourceDir },
28+
);
29+
await spawn(
30+
'xcrun',
31+
[
32+
'devicectl',
33+
'device',
34+
'process',
35+
'launch',
36+
'--device',
37+
selectedDevice.udid,
38+
bundleIdentifier,
39+
],
40+
{ cwd: sourceDir },
41+
);
2542
} catch (error) {
2643
loader.stop(
2744
`Failed: Installing and launching your app on ${color.bold(

packages/platform-apple-helpers/src/lib/utils/buildApp.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import path from 'node:path';
22
import type { IOSProjectConfig } from '@react-native-community/cli-types';
3-
import { RockError } from '@rock-js/tools';
3+
import { getInfoPlist, RockError } from '@rock-js/tools';
44
import type { BuildFlags } from '../commands/build/buildOptions.js';
55
import { buildProject } from '../commands/build/buildProject.js';
66
import { getBuildSettings } from '../commands/run/getBuildSettings.js';
@@ -37,10 +37,15 @@ export async function buildApp({
3737
brownfield?: boolean;
3838
}) {
3939
if (binaryPath) {
40+
// @todo Info.plist is hardcoded when reading from binaryPath
41+
const infoPlistPath = path.join(binaryPath, 'Info.plist');
42+
const infoPlistJson = await getInfoPlist(infoPlistPath);
43+
const bundleIdentifier = infoPlistJson?.['CFBundleIdentifier'] ?? 'unknown';
44+
4045
return {
4146
appPath: binaryPath,
42-
// @todo Info.plist is hardcoded when reading from binaryPath
43-
infoPlistPath: path.join(binaryPath, 'Info.plist'),
47+
bundleIdentifier,
48+
infoPlistPath,
4449
scheme: args.scheme,
4550
xcodeProject: projectConfig.xcodeProject,
4651
sourceDir: projectConfig.sourceDir,
@@ -117,6 +122,7 @@ export async function buildApp({
117122
scheme: scheme,
118123
xcodeProject,
119124
sourceDir,
125+
bundleIdentifier: buildSettings.bundleIdentifier,
120126
};
121127
}
122128

packages/tools/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ export {
2121
handleDownloadResponse,
2222
handleUploadResponse,
2323
} from './lib/build-cache/fetchCachedBuild.js';
24+
export { getInfoPlist } from './lib/getInfoPlist.js';
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { logger, spawn } from '../index.js';
2+
3+
export async function getInfoPlist(
4+
infoPlistPath: string,
5+
): Promise<Record<string, any> | null> {
6+
try {
7+
const { stdout } = await spawn(
8+
'plutil',
9+
['-convert', 'json', '-o', '-', infoPlistPath],
10+
{ stdio: 'pipe' },
11+
);
12+
return JSON.parse(stdout);
13+
} catch (error) {
14+
logger.debug(`Failed to get Info.plist: ${error}`);
15+
}
16+
return null;
17+
}

0 commit comments

Comments
 (0)