Skip to content

Commit 15fa2b5

Browse files
hyochanclaude
andcommitted
fix(ios): use post_install hook for OnsideKit visibility instead of subspec
OnsideKit is not on CocoaPods CDN yet, so subspec dependency fails to resolve. Instead, inject a post_install hook that adds OnsideKit's build dir to ExpoIap's SWIFT_INCLUDE_PATHS, making #if canImport(OnsideKit) work without requiring a podspec dependency. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 498680c commit 15fa2b5

File tree

3 files changed

+95
-51
lines changed

3 files changed

+95
-51
lines changed

ios/ExpoIap.podspec

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,9 @@ Pod::Spec.new do |s|
2626
s.dependency 'ExpoModulesCore'
2727
s.dependency 'openiap', "#{versions['apple']}"
2828

29-
s.subspec 'Onside' do |ss|
30-
ss.dependency 'OnsideKit'
31-
end
32-
33-
s.default_subspecs = []
29+
# OnsideKit is optional; added via ensureOnsidePodIOS() in Podfile when modules.onside is enabled
30+
# A post_install hook makes OnsideKit visible to ExpoIap so #if canImport(OnsideKit) works.
31+
# Once OnsideKit is published to CocoaPods CDN, this can be replaced with a subspec dependency.
3432

3533
# Swift/Objective-C compatibility
3634
s.pod_target_xcconfig = {

plugin/__tests__/withIAP.test.ts

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -195,13 +195,16 @@ describe('ensureOnsidePodIOS', () => {
195195
'',
196196
].join('\n');
197197

198-
it('adds both OnsideKit and ExpoIap/Onside pods', () => {
198+
it('adds OnsideKit pod and post_install hook', () => {
199199
const result = ensureOnsidePodIOS(basePodfile);
200200
expect(result).toContain("pod 'OnsideKit'");
201-
expect(result).toContain("pod 'ExpoIap/Onside'");
201+
expect(result).toContain('# [expo-iap] Make OnsideKit visible');
202+
expect(result).toContain('post_install do |installer|');
203+
expect(result).toContain("target.name == 'ExpoIap'");
204+
expect(result).toContain('SWIFT_INCLUDE_PATHS');
202205
});
203206

204-
it('inserts pods inside the target block', () => {
207+
it('inserts OnsideKit pod inside the target block', () => {
205208
const result = ensureOnsidePodIOS(basePodfile);
206209
const targetIndex = result.indexOf("target 'MyApp' do");
207210
const onsideKitIndex = result.indexOf("pod 'OnsideKit'");
@@ -210,38 +213,51 @@ describe('ensureOnsidePodIOS', () => {
210213
expect(onsideKitIndex).toBeLessThan(endIndex);
211214
});
212215

213-
it('skips if both pods already exist', () => {
214-
const podfileWithBoth = [
216+
it('appends into existing post_install block', () => {
217+
const podfileWithPostInstall = [
215218
"target 'MyApp' do",
216-
" pod 'OnsideKit', :podspec => 'https://example.com'",
217-
" pod 'ExpoIap/Onside', :path => '../node_modules/expo-iap/ios'",
219+
" pod 'ExpoModulesCore'",
220+
'end',
221+
'',
222+
'post_install do |installer|',
223+
' # existing hook',
218224
'end',
219225
].join('\n');
220-
const result = ensureOnsidePodIOS(podfileWithBoth);
221-
expect(result).toBe(podfileWithBoth);
226+
const result = ensureOnsidePodIOS(podfileWithPostInstall);
227+
expect(result).toContain("pod 'OnsideKit'");
228+
expect(result).toContain('# [expo-iap] Make OnsideKit visible');
229+
// Should not create a second post_install block
230+
const postInstallCount = (
231+
result.match(/post_install do \|installer\|/g) ?? []
232+
).length;
233+
expect(postInstallCount).toBe(1);
222234
});
223235

224-
it('adds missing ExpoIap/Onside when OnsideKit already exists', () => {
225-
const podfileWithOnsideKit = [
236+
it('skips if OnsideKit and post_install hook already exist', () => {
237+
const podfileComplete = [
226238
"target 'MyApp' do",
227239
" pod 'OnsideKit', :podspec => 'https://example.com'",
228240
'end',
241+
'',
242+
'post_install do |installer|',
243+
' # [expo-iap] Make OnsideKit visible',
244+
'end',
229245
].join('\n');
230-
const result = ensureOnsidePodIOS(podfileWithOnsideKit);
231-
expect(result).toContain("pod 'ExpoIap/Onside'");
232-
expect(result).not.toContain('raw.githubusercontent');
246+
const result = ensureOnsidePodIOS(podfileComplete);
247+
expect(result).toBe(podfileComplete);
233248
});
234249

235-
it('adds missing OnsideKit when ExpoIap/Onside already exists', () => {
236-
const podfileWithSubspec = [
250+
it('adds post_install hook when OnsideKit already exists', () => {
251+
const podfileWithOnsideKit = [
237252
"target 'MyApp' do",
238-
" pod 'ExpoIap/Onside', :path => '../node_modules/expo-iap/ios'",
253+
" pod 'OnsideKit', :podspec => 'https://example.com'",
239254
'end',
240255
].join('\n');
241-
const result = ensureOnsidePodIOS(podfileWithSubspec);
242-
expect(result).toContain("pod 'OnsideKit'");
243-
const subspecCount = (result.match(/pod 'ExpoIap\/Onside'/g) ?? []).length;
244-
expect(subspecCount).toBe(1);
256+
const result = ensureOnsidePodIOS(podfileWithOnsideKit);
257+
expect(result).toContain('# [expo-iap] Make OnsideKit visible');
258+
// Should not add a duplicate OnsideKit pod
259+
const onsideKitCount = (result.match(/pod 'OnsideKit'/g) ?? []).length;
260+
expect(onsideKitCount).toBe(1);
245261
});
246262

247263
it('returns unchanged content when no target block found', () => {
@@ -260,7 +276,7 @@ describe('ensureOnsidePodIOS', () => {
260276

261277
expect(content).toBe(basePodfile);
262278
expect(content).not.toContain("pod 'OnsideKit'");
263-
expect(content).not.toContain("pod 'ExpoIap/Onside'");
279+
expect(content).not.toContain('# [expo-iap] Make OnsideKit visible');
264280
});
265281

266282
it('modifies Podfile when onside is enabled', () => {
@@ -273,6 +289,6 @@ describe('ensureOnsidePodIOS', () => {
273289

274290
expect(content).not.toBe(basePodfile);
275291
expect(content).toContain("pod 'OnsideKit'");
276-
expect(content).toContain("pod 'ExpoIap/Onside'");
292+
expect(content).toContain('# [expo-iap] Make OnsideKit visible');
277293
});
278294
});

plugin/src/withIAP.ts

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -265,42 +265,72 @@ const withIapAndroid: ConfigPlugin<
265265
const ONSIDEKIT_PODSPEC_URL =
266266
'https://raw.githubusercontent.com/onside-io/OnsideKit-iOS/0.5.0/OnsideKit.podspec';
267267

268-
const EXPO_IAP_IOS_PATH = '../node_modules/expo-iap/ios';
268+
// post_install hook that adds OnsideKit's module to ExpoIap's search paths
269+
// so that `#if canImport(OnsideKit)` resolves correctly at build time.
270+
// This is needed because OnsideKit is not yet on CocoaPods CDN, so we cannot
271+
// use a podspec `s.dependency`. Once OnsideKit is on CDN, replace with a subspec.
272+
const ONSIDE_POST_INSTALL_HOOK = `
273+
# [expo-iap] Make OnsideKit visible to ExpoIap for #if canImport(OnsideKit)
274+
installer.pods_project.targets.each do |target|
275+
if target.name == 'ExpoIap'
276+
target.build_configurations.each do |config|
277+
swift_paths = config.build_settings['SWIFT_INCLUDE_PATHS'] || '$(inherited)'
278+
onside_path = '\${PODS_CONFIGURATION_BUILD_DIR}/OnsideKit'
279+
unless swift_paths.include?(onside_path)
280+
config.build_settings['SWIFT_INCLUDE_PATHS'] = "#{swift_paths} #{onside_path}"
281+
end
282+
end
283+
end
284+
end`;
285+
286+
const ONSIDE_POST_INSTALL_MARKER = '# [expo-iap] Make OnsideKit visible';
269287

270288
export const ensureOnsidePodIOS = (content: string): string => {
271289
const alreadyHasOnsideKit = /^\s*pod\s+['"]OnsideKit['"].*$/m.test(content);
272-
const alreadyHasExpoIapOnside = /^\s*pod\s+['"]ExpoIap\/Onside['"].*$/m.test(
273-
content,
274-
);
290+
const alreadyHasPostInstall = content.includes(ONSIDE_POST_INSTALL_MARKER);
275291

276-
if (alreadyHasOnsideKit && alreadyHasExpoIapOnside) {
292+
if (alreadyHasOnsideKit && alreadyHasPostInstall) {
277293
return content;
278294
}
279295

280-
const targetMatch = content.match(/target\s+'[^']+'\s+do\s*\n/);
281-
if (!targetMatch) {
282-
WarningAggregator.addWarningIOS(
283-
'expo-iap',
284-
'Could not find a target block in Podfile when adding ExpoIap/Onside; skipping installation.',
285-
);
286-
return content;
287-
}
296+
let result = content;
288297

289-
let podLines = '';
298+
// 1) Add OnsideKit pod inside the target block
290299
if (!alreadyHasOnsideKit) {
291-
podLines += ` pod 'OnsideKit', :podspec => '${ONSIDEKIT_PODSPEC_URL}'\n`;
292-
}
293-
if (!alreadyHasExpoIapOnside) {
294-
podLines += ` pod 'ExpoIap/Onside', :path => '${EXPO_IAP_IOS_PATH}'\n`;
300+
const targetMatch = result.match(/target\s+'[^']+'\s+do\s*\n/);
301+
if (!targetMatch) {
302+
WarningAggregator.addWarningIOS(
303+
'expo-iap',
304+
'Could not find a target block in Podfile when adding OnsideKit; skipping installation.',
305+
);
306+
return content;
307+
}
308+
309+
const podLine = ` pod 'OnsideKit', :podspec => '${ONSIDEKIT_PODSPEC_URL}'\n`;
310+
const insertIndex = targetMatch.index! + targetMatch[0].length;
311+
result = result.slice(0, insertIndex) + podLine + result.slice(insertIndex);
295312
}
296313

297-
const insertIndex = targetMatch.index! + targetMatch[0].length;
298-
const before = content.slice(0, insertIndex);
299-
const after = content.slice(insertIndex);
314+
// 2) Add post_install hook to make OnsideKit visible to ExpoIap
315+
if (!alreadyHasPostInstall) {
316+
const postInstallMatch = result.match(/post_install\s+do\s+\|installer\|/);
317+
if (postInstallMatch) {
318+
// Append inside existing post_install block
319+
const insertIndex = postInstallMatch.index! + postInstallMatch[0].length;
320+
result =
321+
result.slice(0, insertIndex) +
322+
'\n' +
323+
ONSIDE_POST_INSTALL_HOOK +
324+
result.slice(insertIndex);
325+
} else {
326+
// Create new post_install block
327+
result += `\npost_install do |installer|${ONSIDE_POST_INSTALL_HOOK}\nend\n`;
328+
}
329+
}
300330

301-
logOnce('📦 expo-iap: Added ExpoIap/Onside subspec to Podfile');
331+
logOnce('📦 expo-iap: Added OnsideKit pod and post_install hook to Podfile');
302332

303-
return `${before}${podLines}${after}`;
333+
return result;
304334
};
305335

306336
export type AutolinkState = {expoIap: boolean; onside: boolean};

0 commit comments

Comments
 (0)