@deuna/react-native-sdk 2.1.0-beta.3 → 2.1.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -1
- package/scripts/setup-ios.js +334 -0
package/package.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deuna/react-native-sdk",
|
|
3
|
-
"version": "2.1.0-beta.
|
|
3
|
+
"version": "2.1.0-beta.4",
|
|
4
4
|
"description": "React Native SDK",
|
|
5
|
+
"bin": {
|
|
6
|
+
"deuna-setup-ios": "./scripts/setup-ios.js"
|
|
7
|
+
},
|
|
5
8
|
"source": "./src/index.tsx",
|
|
6
9
|
"react-native": "./src/index.tsx",
|
|
7
10
|
"main": "./lib/module/index.js",
|
|
@@ -31,6 +34,7 @@
|
|
|
31
34
|
"android",
|
|
32
35
|
"ios",
|
|
33
36
|
"cpp",
|
|
37
|
+
"scripts",
|
|
34
38
|
"*.podspec",
|
|
35
39
|
"react-native.config.js",
|
|
36
40
|
"expo-module.config.json",
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const crypto = require('crypto');
|
|
7
|
+
const { execSync } = require('child_process');
|
|
8
|
+
|
|
9
|
+
const GREEN = '\x1b[32m';
|
|
10
|
+
const YELLOW = '\x1b[33m';
|
|
11
|
+
const RED = '\x1b[31m';
|
|
12
|
+
const RESET = '\x1b[0m';
|
|
13
|
+
|
|
14
|
+
const log = (msg) => console.log(`${GREEN}[deuna setup-ios]${RESET} ${msg}`);
|
|
15
|
+
const warn = (msg) => console.log(`${YELLOW}[deuna setup-ios]${RESET} ${msg}`);
|
|
16
|
+
const error = (msg) => { console.error(`${RED}[deuna setup-ios] ERROR:${RESET} ${msg}`); process.exit(1); };
|
|
17
|
+
|
|
18
|
+
// ─── Project detection ────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
function findProjectRoot() {
|
|
21
|
+
let dir = process.cwd();
|
|
22
|
+
while (dir !== path.parse(dir).root) {
|
|
23
|
+
if (fs.existsSync(path.join(dir, 'package.json'))) return dir;
|
|
24
|
+
dir = path.dirname(dir);
|
|
25
|
+
}
|
|
26
|
+
error('Could not find package.json. Run from your React Native project root.');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isExpoProject(root) {
|
|
30
|
+
try {
|
|
31
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
|
|
32
|
+
return !!(pkg.dependencies?.expo || pkg.devDependencies?.expo);
|
|
33
|
+
} catch { return false; }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function findIosDir(root) {
|
|
37
|
+
const iosDir = path.join(root, 'ios');
|
|
38
|
+
if (!fs.existsSync(iosDir)) error('ios/ directory not found. Run from your React Native project root.');
|
|
39
|
+
return iosDir;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function findPodfile(iosDir) {
|
|
43
|
+
const podfile = path.join(iosDir, 'Podfile');
|
|
44
|
+
if (!fs.existsSync(podfile)) error('Podfile not found in ios/');
|
|
45
|
+
return podfile;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function findXcodeproj(iosDir) {
|
|
49
|
+
const entries = fs.readdirSync(iosDir);
|
|
50
|
+
const proj = entries.find(e => e.endsWith('.xcodeproj') && !e.startsWith('Pods'));
|
|
51
|
+
if (!proj) error('No .xcodeproj found in ios/');
|
|
52
|
+
return path.join(iosDir, proj);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getAppName(xcodeproj) {
|
|
56
|
+
return path.basename(xcodeproj, '.xcodeproj');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ─── Podfile manipulation ─────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
function patchPodfile(podfilePath, appName) {
|
|
62
|
+
let content = fs.readFileSync(podfilePath, 'utf8');
|
|
63
|
+
let changed = false;
|
|
64
|
+
|
|
65
|
+
// 1. Bump platform to 16.4 if lower
|
|
66
|
+
content = content.replace(
|
|
67
|
+
/platform\s+:ios,\s+['"]?[\d.]+['"]?/,
|
|
68
|
+
(match) => {
|
|
69
|
+
const versionMatch = match.match(/[\d.]+/);
|
|
70
|
+
const current = versionMatch ? parseFloat(versionMatch[0]) : 0;
|
|
71
|
+
if (current < 16.4) {
|
|
72
|
+
warn(`Bumping platform :ios to '16.4' (was ${current})`);
|
|
73
|
+
changed = true;
|
|
74
|
+
return "platform :ios, '16.4'";
|
|
75
|
+
}
|
|
76
|
+
return match;
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// 2. Add pod declarations after `use_native_modules!` if not already present
|
|
81
|
+
const podsToAdd = [
|
|
82
|
+
`pod 'ExpoModulesJSI', :path => '../node_modules/expo-modules-jsi/apple'`,
|
|
83
|
+
`pod 'ExpoModulesCore', :path => '../node_modules/expo-modules-core'`,
|
|
84
|
+
`pod 'DeunaWalletsModule', :path => '../node_modules/@deuna/react-native-sdk'`,
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
for (const pod of podsToAdd) {
|
|
88
|
+
const podName = pod.match(/pod '([^']+)'/)[1];
|
|
89
|
+
if (!content.includes(`pod '${podName}'`)) {
|
|
90
|
+
content = content.replace(
|
|
91
|
+
/(config\s*=\s*use_native_modules!)/,
|
|
92
|
+
`$1\n\n ${pod}`
|
|
93
|
+
);
|
|
94
|
+
log(`Added: ${pod}`);
|
|
95
|
+
changed = true;
|
|
96
|
+
} else {
|
|
97
|
+
log(`Already present: pod '${podName}'`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 3. Add post_install deployment target overrides if not already present
|
|
102
|
+
const deploymentPatch = `
|
|
103
|
+
# DeunaWalletsModule and ExpoModulesCore/JSI require iOS 16.4+
|
|
104
|
+
if ['DeunaWalletsModule', 'ExpoModulesCore', 'ExpoModulesJSI'].include?(target.name)
|
|
105
|
+
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.4'
|
|
106
|
+
end`;
|
|
107
|
+
|
|
108
|
+
if (!content.includes("DeunaWalletsModule', 'ExpoModulesCore'")) {
|
|
109
|
+
// Try to inject inside an existing `installer.pods_project.targets.each` block
|
|
110
|
+
if (content.includes('installer.pods_project.targets.each')) {
|
|
111
|
+
content = content.replace(
|
|
112
|
+
/(installer\.pods_project\.targets\.each do \|target\|[\s\S]*?target\.build_configurations\.each do \|config\|)/,
|
|
113
|
+
`$1${deploymentPatch}`
|
|
114
|
+
);
|
|
115
|
+
log('Added deployment target overrides to existing post_install block');
|
|
116
|
+
changed = true;
|
|
117
|
+
} else {
|
|
118
|
+
// Inject a minimal post_install block before the last `end`
|
|
119
|
+
const postInstallBlock = `
|
|
120
|
+
post_install do |installer|
|
|
121
|
+
react_native_post_install(
|
|
122
|
+
installer,
|
|
123
|
+
config[:reactNativePath],
|
|
124
|
+
:mac_catalyst_enabled => false,
|
|
125
|
+
)
|
|
126
|
+
installer.pods_project.targets.each do |target|
|
|
127
|
+
target.build_configurations.each do |config|${deploymentPatch}
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end`;
|
|
131
|
+
// Insert before the final `end` of the target block
|
|
132
|
+
content = content.replace(/(^end\s*$)/m, `${postInstallBlock}\n$1`);
|
|
133
|
+
log('Created post_install block with deployment target overrides');
|
|
134
|
+
changed = true;
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
log('Deployment target overrides already present');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (changed) {
|
|
141
|
+
fs.writeFileSync(podfilePath, content, 'utf8');
|
|
142
|
+
log('Podfile updated');
|
|
143
|
+
}
|
|
144
|
+
return changed;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ─── ExpoModulesProvider.swift ────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
function createExpoModulesProvider(iosDir, appName) {
|
|
150
|
+
const targetDir = path.join(iosDir, appName);
|
|
151
|
+
if (!fs.existsSync(targetDir)) {
|
|
152
|
+
warn(`App target directory ios/${appName}/ not found — creating it`);
|
|
153
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const filePath = path.join(targetDir, 'ExpoModulesProvider.swift');
|
|
157
|
+
if (fs.existsSync(filePath)) {
|
|
158
|
+
log('ExpoModulesProvider.swift already exists');
|
|
159
|
+
return { filePath, created: false };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const content = `import ExpoModulesCore
|
|
163
|
+
import DeunaWalletsModule
|
|
164
|
+
|
|
165
|
+
@objc(ExpoModulesProvider)
|
|
166
|
+
public class ExpoModulesProvider: ModulesProvider {
|
|
167
|
+
public override func getModuleClasses() -> [ExpoModuleTupleType] {
|
|
168
|
+
return [
|
|
169
|
+
(module: DeunaWalletsModule.self, name: nil)
|
|
170
|
+
]
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
public override func getAppDelegateSubscribers() -> [ExpoAppDelegateSubscriber.Type] {
|
|
174
|
+
return []
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
public override func getReactDelegateHandlers() -> [ExpoReactDelegateHandlerTupleType] {
|
|
178
|
+
return []
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
public override func getAppCodeSignEntitlements() -> AppCodeSignEntitlements {
|
|
182
|
+
return AppCodeSignEntitlements.from(json: #"{}"#)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
`;
|
|
186
|
+
|
|
187
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
188
|
+
log(`Created ExpoModulesProvider.swift at ios/${appName}/ExpoModulesProvider.swift`);
|
|
189
|
+
return { filePath, created: true };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ─── project.pbxproj manipulation ────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
function generateUUID() {
|
|
195
|
+
return crypto.randomBytes(12).toString('hex').toUpperCase();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function addFileToPbxproj(xcodeproj, appName, swiftRelativePath) {
|
|
199
|
+
const pbxprojPath = path.join(xcodeproj, 'project.pbxproj');
|
|
200
|
+
let content = fs.readFileSync(pbxprojPath, 'utf8');
|
|
201
|
+
|
|
202
|
+
if (content.includes('ExpoModulesProvider.swift')) {
|
|
203
|
+
log('ExpoModulesProvider.swift already in project.pbxproj');
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const fileRefUUID = generateUUID();
|
|
208
|
+
const buildFileUUID = generateUUID();
|
|
209
|
+
const fileName = 'ExpoModulesProvider.swift';
|
|
210
|
+
const filePath = `${appName}/${fileName}`;
|
|
211
|
+
|
|
212
|
+
// 1. Add PBXBuildFile entry
|
|
213
|
+
content = content.replace(
|
|
214
|
+
/(\s*\/\* End PBXBuildFile section \*\/)/,
|
|
215
|
+
`\t\t${buildFileUUID} /* ${fileName} in Sources */ = {isa = PBXBuildFile; fileRef = ${fileRefUUID} /* ${fileName} */; };$1`
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
// 2. Add PBXFileReference entry
|
|
219
|
+
content = content.replace(
|
|
220
|
+
/(\s*\/\* End PBXFileReference section \*\/)/,
|
|
221
|
+
`\t\t${fileRefUUID} /* ${fileName} */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ${fileName}; path = ${filePath}; sourceTree = "<group>"; };$1`
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
// 3. Add to app group (find group that contains AppDelegate.swift)
|
|
225
|
+
content = content.replace(
|
|
226
|
+
/(AppDelegate\.swift \*\/,\s*\n)/,
|
|
227
|
+
`$1\t\t\t\t${fileRefUUID} /* ${fileName} */,\n`
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
// 4. Add to Sources build phase
|
|
231
|
+
content = content.replace(
|
|
232
|
+
/(AppDelegate\.swift in Sources \*\/,\s*\n)/,
|
|
233
|
+
`$1\t\t\t\t${buildFileUUID} /* ${fileName} in Sources */,\n`
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
fs.writeFileSync(pbxprojPath, content, 'utf8');
|
|
237
|
+
log(`Added ExpoModulesProvider.swift to project.pbxproj`);
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ─── npm deps check ───────────────────────────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
function checkNpmDeps(root) {
|
|
244
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
|
|
245
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
246
|
+
const missing = [];
|
|
247
|
+
|
|
248
|
+
if (!allDeps['expo-modules-core']) missing.push('expo-modules-core@56.0.5');
|
|
249
|
+
if (!allDeps['expo-modules-jsi']) missing.push('expo-modules-jsi@56.0.2');
|
|
250
|
+
|
|
251
|
+
if (missing.length > 0) {
|
|
252
|
+
log(`Installing missing npm packages: ${missing.join(', ')}`);
|
|
253
|
+
execSync(`npm install ${missing.join(' ')} --save`, { cwd: root, stdio: 'inherit' });
|
|
254
|
+
} else {
|
|
255
|
+
log('npm deps already installed');
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function patchExpoModulesCorepodspec(root) {
|
|
260
|
+
const podspecPath = path.join(root, 'node_modules', 'expo-modules-core', 'ExpoModulesCore.podspec');
|
|
261
|
+
if (!fs.existsSync(podspecPath)) {
|
|
262
|
+
warn('ExpoModulesCore.podspec not found — skipping patch');
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
let content = fs.readFileSync(podspecPath, 'utf8');
|
|
267
|
+
const needle = 'if (!Expo::PackagesConfig.instance.try_link_with_prebuilt_xcframework(s))';
|
|
268
|
+
const replacement = 'if (!defined?(Expo::PackagesConfig) || !Expo::PackagesConfig.instance.try_link_with_prebuilt_xcframework(s))';
|
|
269
|
+
|
|
270
|
+
if (content.includes(needle)) {
|
|
271
|
+
content = content.replace(needle, replacement);
|
|
272
|
+
fs.writeFileSync(podspecPath, content, 'utf8');
|
|
273
|
+
log('Patched ExpoModulesCore.podspec (Expo:: gem guard)');
|
|
274
|
+
} else if (content.includes(replacement)) {
|
|
275
|
+
log('ExpoModulesCore.podspec already patched');
|
|
276
|
+
} else {
|
|
277
|
+
warn('ExpoModulesCore.podspec: unexpected format — skipping patch');
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ─── Main ─────────────────────────────────────────────────────────────────────
|
|
282
|
+
|
|
283
|
+
function main() {
|
|
284
|
+
const root = findProjectRoot();
|
|
285
|
+
log(`Project root: ${root}`);
|
|
286
|
+
|
|
287
|
+
if (isExpoProject(root)) {
|
|
288
|
+
log('Expo project detected — no setup needed. Run: npx expo prebuild');
|
|
289
|
+
process.exit(0);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
log('React Native CLI project detected — running iOS setup...\n');
|
|
293
|
+
|
|
294
|
+
// 1. npm deps
|
|
295
|
+
checkNpmDeps(root);
|
|
296
|
+
|
|
297
|
+
// 2. Patch ExpoModulesCore podspec
|
|
298
|
+
patchExpoModulesCorepodspec(root);
|
|
299
|
+
|
|
300
|
+
// 3. Podfile
|
|
301
|
+
const iosDir = findIosDir(root);
|
|
302
|
+
const podfilePath = findPodfile(iosDir);
|
|
303
|
+
const xcodeproj = findXcodeproj(iosDir);
|
|
304
|
+
const appName = getAppName(xcodeproj);
|
|
305
|
+
|
|
306
|
+
log(`App target: ${appName}`);
|
|
307
|
+
patchPodfile(podfilePath, appName);
|
|
308
|
+
|
|
309
|
+
// 4. ExpoModulesProvider.swift
|
|
310
|
+
const { created } = createExpoModulesProvider(iosDir, appName);
|
|
311
|
+
|
|
312
|
+
// 5. Add to project.pbxproj
|
|
313
|
+
if (created) {
|
|
314
|
+
addFileToPbxproj(xcodeproj, appName, `${appName}/ExpoModulesProvider.swift`);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// 6. pod install
|
|
318
|
+
log('\nRunning pod install...');
|
|
319
|
+
try {
|
|
320
|
+
execSync('pod install', {
|
|
321
|
+
cwd: iosDir,
|
|
322
|
+
stdio: 'inherit',
|
|
323
|
+
env: { ...process.env, LANG: 'en_US.UTF-8' },
|
|
324
|
+
});
|
|
325
|
+
} catch (e) {
|
|
326
|
+
error('pod install failed. Check the output above for details.');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
console.log(`\n${GREEN}✓ iOS setup complete!${RESET}`);
|
|
330
|
+
console.log(`\nNext steps:`);
|
|
331
|
+
console.log(` npx react-native run-ios`);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
main();
|