@react-native-native/cli 0.1.0

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.
@@ -0,0 +1,314 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * setup-kotlin.js — Sets up basic Kotlin dev hot-reload toolchain.
5
+ *
6
+ * Downloads the embeddable Kotlin compiler + stdlib from Maven into
7
+ * the Gradle cache (or a local dir) so the Kotlin daemon can start
8
+ * without needing a full Android build first.
9
+ *
10
+ * Run: npx @react-native-native/cli setup-kotlin
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const https = require('https');
16
+ const readline = require('readline');
17
+
18
+ const gradleCache = path.join(process.env.HOME || '', '.gradle/caches/modules-2/files-2.1');
19
+
20
+ // ── Kotlin version detection ──────────────────────────────────────────
21
+
22
+ function detectKotlinVersion(projectRoot) {
23
+ // 1. app.json → expo-build-properties override
24
+ try {
25
+ const appJson = JSON.parse(fs.readFileSync(path.join(projectRoot, 'app.json'), 'utf8'));
26
+ const plugins = appJson?.expo?.plugins || [];
27
+ for (const p of plugins) {
28
+ if (Array.isArray(p) && p[0] === 'expo-build-properties') {
29
+ const v = p[1]?.android?.kotlinVersion;
30
+ if (v) return { version: v, source: 'expo-build-properties' };
31
+ }
32
+ }
33
+ } catch {}
34
+
35
+ // 2. nativ-fabric build.gradle → the version the package actually uses
36
+ const nativFabricPaths = [
37
+ path.join(projectRoot, 'node_modules/@react-native-native/nativ-fabric/android/build.gradle'),
38
+ // Hoisted in monorepo
39
+ ...(() => {
40
+ const results = [];
41
+ let dir = projectRoot;
42
+ for (let i = 0; i < 5; i++) {
43
+ dir = path.dirname(dir);
44
+ results.push(path.join(dir, 'node_modules/@react-native-native/nativ-fabric/android/build.gradle'));
45
+ }
46
+ return results;
47
+ })(),
48
+ ];
49
+ for (const p of nativFabricPaths) {
50
+ try {
51
+ const content = fs.readFileSync(p, 'utf8');
52
+ const m = content.match(/kotlinVersion\s*:\s*"([^"]+)"/);
53
+ if (m) return { version: m[1], source: 'nativ-fabric' };
54
+ } catch {}
55
+ }
56
+
57
+ // 3. expo-modules-core → default Kotlin version for this SDK
58
+ const expoCorePaths = [
59
+ path.join(projectRoot, 'node_modules/expo-modules-core/android/ExpoModulesCorePlugin.gradle'),
60
+ // Hoisted in monorepo
61
+ ...(() => {
62
+ const results = [];
63
+ let dir = projectRoot;
64
+ for (let i = 0; i < 5; i++) {
65
+ dir = path.dirname(dir);
66
+ results.push(path.join(dir, 'node_modules/expo-modules-core/android/ExpoModulesCorePlugin.gradle'));
67
+ }
68
+ return results;
69
+ })(),
70
+ ];
71
+ for (const p of expoCorePaths) {
72
+ try {
73
+ const content = fs.readFileSync(p, 'utf8');
74
+ // Parse: ? project.rootProject.ext.get("kotlinVersion") : "2.0.21"
75
+ const m = content.match(/project\.rootProject\.ext\.get\("kotlinVersion"\)\s*:\s*"([^"]+)"/);
76
+ if (m) return { version: m[1], source: 'expo-modules-core' };
77
+ } catch {}
78
+ }
79
+
80
+ // 3. Existing android/build.gradle
81
+ try {
82
+ const bg = fs.readFileSync(path.join(projectRoot, 'android/build.gradle'), 'utf8');
83
+ const m = bg.match(/kotlinVersion\s*=\s*["']([^"']+)["']/);
84
+ if (m) return { version: m[1], source: 'android/build.gradle' };
85
+ } catch {}
86
+
87
+ return null;
88
+ }
89
+
90
+ function readConfig(projectRoot) {
91
+ const configPath = path.join(projectRoot, '.nativ/nativ.config.json');
92
+ try { return JSON.parse(fs.readFileSync(configPath, 'utf8')); } catch {}
93
+ return {};
94
+ }
95
+
96
+ function writeConfig(projectRoot, config) {
97
+ const configPath = path.join(projectRoot, '.nativ/nativ.config.json');
98
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
99
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
100
+ }
101
+
102
+ function ask(question) {
103
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
104
+ return new Promise(resolve => rl.question(question, answer => { rl.close(); resolve(answer.trim()); }));
105
+ }
106
+
107
+ function download(url, dest) {
108
+ return new Promise((resolve, reject) => {
109
+ console.log(` Downloading ${path.basename(dest)}...`);
110
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
111
+ const file = fs.createWriteStream(dest);
112
+ const request = https.get(url, (response) => {
113
+ if (response.statusCode === 301 || response.statusCode === 302) {
114
+ file.close();
115
+ fs.unlinkSync(dest);
116
+ return download(response.headers.location, dest).then(resolve, reject);
117
+ }
118
+ if (response.statusCode !== 200) {
119
+ file.close();
120
+ fs.unlinkSync(dest);
121
+ return reject(new Error(`HTTP ${response.statusCode}`));
122
+ }
123
+ response.pipe(file);
124
+ file.on('finish', () => { file.close(); resolve(); });
125
+ });
126
+ request.on('error', (err) => {
127
+ try { fs.unlinkSync(dest); } catch {}
128
+ reject(err);
129
+ });
130
+ });
131
+ }
132
+
133
+ function findJar(group, artifact) {
134
+ const dir = path.join(gradleCache, group, artifact);
135
+ if (!fs.existsSync(dir)) return null;
136
+ try {
137
+ const { execSync } = require('child_process');
138
+ return execSync(
139
+ `find "${dir}" -name "*.jar" -not -name "*sources*" -not -name "*javadoc*" 2>/dev/null | sort -V | tail -1`,
140
+ { encoding: 'utf8' }
141
+ ).trim() || null;
142
+ } catch { return null; }
143
+ }
144
+
145
+ const MAVEN = 'https://repo1.maven.org/maven2';
146
+
147
+ const jars = [
148
+ ['org.jetbrains.kotlin', 'kotlin-compiler-embeddable'],
149
+ ['org.jetbrains.kotlin', 'kotlin-stdlib'],
150
+ ['org.jetbrains.kotlin', 'kotlin-script-runtime'],
151
+ ['org.jetbrains.kotlinx', 'kotlinx-coroutines-core-jvm'],
152
+ ['org.jetbrains.intellij.deps', 'trove4j'],
153
+ ['org.jetbrains', 'annotations'],
154
+ ];
155
+
156
+ async function main() {
157
+ console.log('React Native Native — Kotlin dev toolchain setup\n');
158
+
159
+ const projectRoot = process.cwd();
160
+ const config = readConfig(projectRoot);
161
+
162
+ // ── Detect or ask for Kotlin version ────────────────────────────────
163
+ let kotlinVersion = config.kotlin?.version;
164
+ if (!kotlinVersion) {
165
+ const detected = detectKotlinVersion(projectRoot);
166
+ if (detected) {
167
+ console.log(` Detected Kotlin ${detected.version} (from ${detected.source})`);
168
+ const answer = await ask(` Use this version? [Y/n] `);
169
+ kotlinVersion = (answer.toLowerCase() === 'n') ? null : detected.version;
170
+ }
171
+ if (!kotlinVersion) {
172
+ const answer = await ask(` Enter Kotlin version [2.0.21]: `);
173
+ kotlinVersion = answer || '2.0.21';
174
+ }
175
+ config.kotlin = { ...config.kotlin, version: kotlinVersion };
176
+ writeConfig(projectRoot, config);
177
+ console.log(`✓ Kotlin version: ${kotlinVersion} (saved to .nativ/nativ.config.json)\n`);
178
+ } else {
179
+ console.log(`✓ Kotlin version: ${kotlinVersion} (from .nativ/nativ.config.json)\n`);
180
+ }
181
+
182
+ const KOTLIN_VERSION = kotlinVersion;
183
+ let allFound = true;
184
+
185
+ for (const [group, artifact] of jars) {
186
+ const existing = findJar(group, artifact);
187
+ if (existing) {
188
+ console.log(`✓ ${artifact} (in Gradle cache)`);
189
+ continue;
190
+ }
191
+
192
+ allFound = false;
193
+
194
+ // Download to a local cache dir
195
+ const localDir = path.join(process.cwd(), '.nativ/kotlin-cache');
196
+ fs.mkdirSync(localDir, { recursive: true });
197
+
198
+ // Determine version — most are Kotlin version, some differ
199
+ let version = KOTLIN_VERSION;
200
+ if (artifact === 'trove4j') version = '1.0.20200330';
201
+ if (artifact === 'annotations') version = '13.0';
202
+ if (artifact === 'kotlinx-coroutines-core-jvm') version = '1.9.0';
203
+
204
+ const groupPath = group.replace(/\./g, '/');
205
+ const url = `${MAVEN}/${groupPath}/${artifact}/${version}/${artifact}-${version}.jar`;
206
+ const dest = path.join(localDir, `${artifact}-${version}.jar`);
207
+
208
+ if (fs.existsSync(dest)) {
209
+ console.log(`✓ ${artifact} (in local cache)`);
210
+ continue;
211
+ }
212
+
213
+ try {
214
+ await download(url, dest);
215
+ const size = fs.statSync(dest).size;
216
+ console.log(`✓ ${artifact}-${version}.jar (${(size / 1024 / 1024).toFixed(1)}MB)`);
217
+ } catch (e) {
218
+ console.error(`✗ Failed to download ${artifact}: ${e.message}`);
219
+ }
220
+ }
221
+
222
+ // ── android.jar (API stubs for type resolution) ──────────────────────
223
+ const androidHome = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT
224
+ || path.join(process.env.HOME || '', 'Library/Android/sdk');
225
+ const platformsDir = path.join(androidHome, 'platforms');
226
+ let hasAndroidJar = false;
227
+
228
+ if (fs.existsSync(platformsDir)) {
229
+ const platforms = fs.readdirSync(platformsDir).sort();
230
+ if (platforms.length > 0) {
231
+ const jar = path.join(platformsDir, platforms[platforms.length - 1], 'android.jar');
232
+ if (fs.existsSync(jar)) {
233
+ console.log(`✓ android.jar (${platforms[platforms.length - 1]})`);
234
+ hasAndroidJar = true;
235
+ }
236
+ }
237
+ }
238
+
239
+ if (!hasAndroidJar) {
240
+ // Download a minimal android.jar from Google's Maven
241
+ const localDir = path.join(process.cwd(), '.nativ/kotlin-cache');
242
+ const androidJarDest = path.join(localDir, 'android.jar');
243
+ if (fs.existsSync(androidJarDest)) {
244
+ console.log(`✓ android.jar (in local cache)`);
245
+ } else {
246
+ console.log(' Android SDK not found — downloading android.jar...');
247
+ // android.jar from the SDK's platforms dir. Google hosts them at dl.google.com.
248
+ // We use the android-34 platform as a stable baseline.
249
+ const androidJarUrl = 'https://dl.google.com/android/repository/platform-34_r03.zip';
250
+ const zipDest = path.join(localDir, 'platform-34.zip');
251
+ try {
252
+ await download(androidJarUrl, zipDest);
253
+ // Extract just android.jar from the zip
254
+ const { execSync } = require('child_process');
255
+ execSync(`unzip -o -q "${zipDest}" "android-34/android.jar" -d "${localDir}" 2>/dev/null`, { stdio: 'pipe' });
256
+ const extracted = path.join(localDir, 'android-34/android.jar');
257
+ if (fs.existsSync(extracted)) {
258
+ fs.renameSync(extracted, androidJarDest);
259
+ try { fs.rmdirSync(path.join(localDir, 'android-34')); } catch {}
260
+ }
261
+ try { fs.unlinkSync(zipDest); } catch {}
262
+ if (fs.existsSync(androidJarDest)) {
263
+ const size = fs.statSync(androidJarDest).size;
264
+ console.log(`✓ android.jar (${(size / 1024 / 1024).toFixed(1)}MB, downloaded)`);
265
+ }
266
+ } catch (e) {
267
+ console.warn(`⚠ Failed to download android.jar: ${e.message}`);
268
+ console.warn(' Install Android Studio or set $ANDROID_HOME');
269
+ }
270
+ }
271
+ }
272
+
273
+ // ── d8 (.class → .dex conversion) ───────────────────────────────────
274
+ const btDir = path.join(androidHome, 'build-tools');
275
+ let hasD8 = false;
276
+
277
+ if (fs.existsSync(btDir)) {
278
+ const versions = fs.readdirSync(btDir).sort();
279
+ if (versions.length > 0) {
280
+ console.log(`✓ d8 (Android build-tools ${versions[versions.length - 1]})`);
281
+ hasD8 = true;
282
+ }
283
+ }
284
+
285
+ if (!hasD8) {
286
+ // Download d8 (R8) from Maven — it's a standalone JAR
287
+ const localDir = path.join(process.cwd(), '.nativ/kotlin-cache');
288
+ const d8Dest = path.join(localDir, 'd8.jar');
289
+ if (fs.existsSync(d8Dest)) {
290
+ console.log(`✓ d8 (in local cache)`);
291
+ } else {
292
+ const R8_VERSION = '8.5.35';
293
+ const d8Url = `${MAVEN}/com/android/tools/r8/${R8_VERSION}/r8-${R8_VERSION}.jar`;
294
+ try {
295
+ await download(d8Url, d8Dest);
296
+ const size = fs.statSync(d8Dest).size;
297
+ console.log(`✓ d8/r8 (${(size / 1024 / 1024).toFixed(1)}MB, downloaded)`);
298
+ } catch (e) {
299
+ console.warn(`⚠ Failed to download d8: ${e.message}`);
300
+ console.warn(' Install Android Studio or set $ANDROID_HOME');
301
+ }
302
+ }
303
+ }
304
+
305
+ console.log('\nDone. Kotlin hot-reload is ready.');
306
+ if (!allFound || !hasAndroidJar || !hasD8) {
307
+ console.log('Note: Missing tools were cached in .nativ/kotlin-cache/');
308
+ }
309
+ }
310
+
311
+ main().catch(e => {
312
+ console.error('Setup failed:', e.message);
313
+ process.exit(1);
314
+ });
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * setup-rust.js — Sets up Rust toolchain for native hot-reload.
5
+ *
6
+ * - Verifies Rust is installed (rustc + cargo)
7
+ * - Adds iOS and Android cross-compilation targets
8
+ * - Creates Cargo.toml with nativ-core dependency from crates.io
9
+ *
10
+ * Run: npx @react-native-native/cli setup-rust
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const { execSync } = require('child_process');
16
+
17
+ function run(cmd) {
18
+ try {
19
+ return execSync(cmd, { encoding: 'utf8', stdio: 'pipe' }).trim();
20
+ } catch { return null; }
21
+ }
22
+
23
+ async function main() {
24
+ console.log('React Native Native — Rust dev toolchain setup\n');
25
+
26
+ const projectRoot = process.cwd();
27
+
28
+ // ── 1. Check Rust toolchain ─────────────────────────────────────────
29
+ const rustcVersion = run('rustc --version');
30
+ if (!rustcVersion) {
31
+ console.error('✗ Rust not found.');
32
+ console.error(' Install: curl --proto \'=https\' --tlsv1.2 -sSf https://sh.rustup.rs | sh');
33
+ process.exit(1);
34
+ }
35
+ console.log(`✓ ${rustcVersion}`);
36
+
37
+ const cargoVersion = run('cargo --version');
38
+ if (cargoVersion) console.log(`✓ ${cargoVersion}`);
39
+
40
+ // ── 2. Check/install cross-compilation targets ──────────────────────
41
+ const installed = run('rustup target list --installed') || '';
42
+
43
+ const platformArg = process.argv.find((a, i) => i > 0 && process.argv[i - 1] === '--platform') || 'all';
44
+ const wantIOS = platformArg === 'all' || platformArg === 'ios';
45
+ const wantAndroid = platformArg === 'all' || platformArg === 'android';
46
+
47
+ const targets = [
48
+ ...(wantIOS ? [
49
+ 'aarch64-apple-ios', // iOS device + App Store
50
+ 'aarch64-apple-ios-sim', // iOS simulator (Apple Silicon)
51
+ ] : []),
52
+ ...(wantAndroid ? [
53
+ 'aarch64-linux-android', // Android device
54
+ 'armv7-linux-androideabi', // Android device (older 32-bit)
55
+ 'x86_64-linux-android', // Android emulator
56
+ ] : []),
57
+ ];
58
+
59
+ for (const target of targets) {
60
+ if (installed.includes(target)) {
61
+ console.log(`✓ Target ${target}`);
62
+ } else {
63
+ console.log(` Adding target ${target}...`);
64
+ try {
65
+ execSync(`rustup target add ${target}`, { stdio: 'inherit' });
66
+ console.log(`✓ Target ${target}`);
67
+ } catch {
68
+ console.warn(`⚠ Failed to add ${target}. Run manually: rustup target add ${target}`);
69
+ }
70
+ }
71
+ }
72
+
73
+ // ── 3. Create Cargo.toml ────────────────────────────────────────────
74
+ const cargoTomlPath = path.join(projectRoot, 'Cargo.toml');
75
+ if (fs.existsSync(cargoTomlPath)) {
76
+ console.log('✓ Cargo.toml already exists');
77
+ // Verify nativ-core dependency is present
78
+ const content = fs.readFileSync(cargoTomlPath, 'utf8');
79
+ if (!content.includes('nativ-core')) {
80
+ console.warn('⚠ Cargo.toml is missing nativ-core dependency. Add:');
81
+ console.warn(' nativ-core = "0.1"');
82
+ }
83
+ } else {
84
+ const cargoToml = `[package]
85
+ name = "native"
86
+ version = "0.1.0"
87
+ edition = "2024"
88
+
89
+ [lib]
90
+ path = ".nativ/lib.rs"
91
+
92
+ [workspace]
93
+
94
+ [dependencies]
95
+ nativ-core = "0.1"
96
+
97
+ # iOS-only dependencies — e.g. objc2
98
+ # [target.'cfg(target_os = "ios")'.dependencies]
99
+
100
+ # Android-only dependencies — e.g. jni
101
+ # [target.'cfg(target_os = "android")'.dependencies]
102
+ `;
103
+ fs.writeFileSync(cargoTomlPath, cargoToml);
104
+ console.log('✓ Created Cargo.toml (nativ-core = "0.1")');
105
+ }
106
+
107
+ // Always ensure .nativ/lib.rs exists (Cargo requires a lib target)
108
+ const libRsPath = path.join(projectRoot, '.nativ/lib.rs');
109
+ if (!fs.existsSync(libRsPath)) {
110
+ fs.mkdirSync(path.join(projectRoot, '.nativ'), { recursive: true });
111
+ fs.writeFileSync(libRsPath,
112
+ '// Stub for rust-analyzer and `cargo add`. Do not edit.\n');
113
+ console.log('✓ Created .nativ/lib.rs');
114
+ }
115
+
116
+ console.log('\nDone. Rust hot-reload is ready.');
117
+ console.log('Add .rs files to src/ and they\'ll compile on save.');
118
+ }
119
+
120
+ main().catch(e => {
121
+ console.error('Setup failed:', e.message);
122
+ process.exit(1);
123
+ });
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+
3
+ const consola = require('consola');
4
+ const { execSync } = require('child_process');
5
+ const path = require('path');
6
+ const { configureProject } = require('./project-config');
7
+
8
+ async function main() {
9
+ consola.box('React Native Native setup');
10
+
11
+ // Configure project files (.gitignore, tsconfig)
12
+ configureProject(process.cwd());
13
+
14
+ const platforms = await consola.prompt('Which platforms?', {
15
+ type: 'multiselect',
16
+ options: ['ios', 'android'],
17
+ initial: ['ios', 'android'],
18
+ required: true,
19
+ });
20
+ if (typeof platforms === 'symbol') process.exit(0);
21
+
22
+ const languages = await consola.prompt('Which languages?', {
23
+ type: 'multiselect',
24
+ options: [
25
+ { value: 'cpp', label: 'C++ / ObjC++', hint: 'no setup needed' },
26
+ { value: 'swift', label: 'Swift', hint: 'iOS only, no setup needed' },
27
+ { value: 'rust', label: 'Rust', hint: 'installs targets + Cargo.toml' },
28
+ { value: 'kotlin', label: 'Kotlin', hint: 'downloads compiler' },
29
+ { value: 'compose', label: 'Jetpack Compose', hint: 'includes Kotlin' },
30
+ ],
31
+ required: true,
32
+ });
33
+ if (typeof languages === 'symbol') process.exit(0);
34
+
35
+ const platformFlag = platforms.length === 1 ? `--platform ${platforms[0]}` : '';
36
+ const scriptsDir = __dirname;
37
+
38
+ if (languages.includes('rust')) {
39
+ consola.start('Setting up Rust...');
40
+ try {
41
+ execSync(`node ${path.join(scriptsDir, 'setup-rust.js')} ${platformFlag}`, {
42
+ stdio: 'inherit',
43
+ cwd: process.cwd(),
44
+ });
45
+ consola.success('Rust ready');
46
+ } catch {
47
+ consola.error('Rust setup failed');
48
+ }
49
+ }
50
+
51
+ if (languages.includes('compose')) {
52
+ // Kotlin version detection must run first — compose reads from nativ.config.json
53
+ consola.start('Setting up Kotlin...');
54
+ try {
55
+ execSync(`node ${path.join(scriptsDir, 'setup-kotlin.js')}`, {
56
+ stdio: 'inherit',
57
+ cwd: process.cwd(),
58
+ });
59
+ } catch {}
60
+ consola.start('Setting up Compose...');
61
+ try {
62
+ execSync(`node ${path.join(scriptsDir, 'setup-compose.js')}`, {
63
+ stdio: 'inherit',
64
+ cwd: process.cwd(),
65
+ });
66
+ consola.success('Kotlin + Compose ready');
67
+ } catch {
68
+ consola.error('Kotlin + Compose setup failed');
69
+ }
70
+ } else if (languages.includes('kotlin')) {
71
+ consola.start('Setting up Kotlin...');
72
+ try {
73
+ execSync(`node ${path.join(scriptsDir, 'setup-kotlin.js')}`, {
74
+ stdio: 'inherit',
75
+ cwd: process.cwd(),
76
+ });
77
+ consola.success('Kotlin ready');
78
+ } catch {
79
+ consola.error('Kotlin setup failed');
80
+ }
81
+ }
82
+
83
+ const noSetup = [];
84
+ if (languages.includes('cpp')) noSetup.push('C++/ObjC++');
85
+ if (languages.includes('swift')) noSetup.push('Swift');
86
+ if (noSetup.length > 0) {
87
+ consola.info(`${noSetup.join(' and ')} — no additional setup needed`);
88
+ }
89
+
90
+ const runDoctor = await consola.prompt('Run doctor to verify?', {
91
+ type: 'confirm',
92
+ initial: true,
93
+ });
94
+ if (runDoctor === true) {
95
+ execSync(`node ${path.join(scriptsDir, 'doctor.js')} ${platformFlag}`, {
96
+ stdio: 'inherit',
97
+ cwd: process.cwd(),
98
+ });
99
+ }
100
+
101
+ consola.success('Setup complete!');
102
+ }
103
+
104
+ main().catch(e => {
105
+ consola.error('Setup failed:', e.message);
106
+ process.exit(1);
107
+ });