@rejaul/react-native-rust 1.0.1 → 1.1.1

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,158 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var spec_exports = {};
20
+ __export(spec_exports, {
21
+ callbackInfo: () => callbackInfo,
22
+ parseSpec: () => parseSpec,
23
+ toSnakeCase: () => toSnakeCase,
24
+ typeInfo: () => typeInfo,
25
+ validateIdentifier: () => validateIdentifier
26
+ });
27
+ module.exports = __toCommonJS(spec_exports);
28
+ var import_constants = require("./constants");
29
+ function typeInfo(typeNode, sourceFile, methodName, role) {
30
+ const info = import_constants.supportedTypes.get(typeNode.kind);
31
+ if (info && !(role === "parameter" && typeNode.kind === import_constants.ts.SyntaxKind.VoidKeyword)) return info;
32
+ if (role === "return" && import_constants.ts.isTypeReferenceNode(typeNode) && typeNode.typeName.getText(sourceFile) === "Promise") {
33
+ if (typeNode.typeArguments?.length !== 1) throw new Error(`Promise return in ${methodName}() must declare one result type.`);
34
+ const result = typeInfo(typeNode.typeArguments[0], sourceFile, methodName, "promise result");
35
+ if (result.promise) {
36
+ throw new Error(`Nested Promise or callback results are not supported in ${methodName}().`);
37
+ }
38
+ return { ...result, typescript: typeNode.getText(sourceFile), promise: true };
39
+ }
40
+ const typeName = import_constants.ts.isTypeReferenceNode(typeNode) ? typeNode.typeName.getText(sourceFile) : "";
41
+ const objectType = typeNode.kind === import_constants.ts.SyntaxKind.ObjectKeyword || ["CodegenTypes.UnsafeObject", "UnsafeObject"].includes(typeName);
42
+ if (objectType) {
43
+ return {
44
+ kind: "json",
45
+ typescript: typeNode.getText(sourceFile),
46
+ rust: "serde_json::Value",
47
+ cpp: "jsi::Object",
48
+ defaultValue: "serde_json::json!({})"
49
+ };
50
+ }
51
+ const arrayElement = import_constants.ts.isArrayTypeNode(typeNode) ? typeNode.elementType : import_constants.ts.isTypeReferenceNode(typeNode) && ["Array", "ReadonlyArray"].includes(typeNode.typeName.getText(sourceFile)) && typeNode.typeArguments?.length === 1 ? typeNode.typeArguments[0] : null;
52
+ if (arrayElement) {
53
+ const elementType = typeInfo(arrayElement, sourceFile, methodName, "array element");
54
+ if (elementType.promise || elementType.kind === "void") {
55
+ throw new Error(`Unsupported array element type in ${methodName}().`);
56
+ }
57
+ return {
58
+ kind: "json",
59
+ typescript: typeNode.getText(sourceFile),
60
+ rust: "serde_json::Value",
61
+ cpp: "jsi::Array",
62
+ defaultValue: "serde_json::json!([])"
63
+ };
64
+ }
65
+ const text = typeNode.getText(sourceFile);
66
+ throw new Error(`Unsupported ${role} type "${text}" in ${methodName}(). Supported types are number, boolean, string, arrays, and CodegenTypes.UnsafeObject${role === "return" ? ", plus void and Promise<T>" : ""}.`);
67
+ }
68
+ function callbackInfo(typeNode, sourceFile, methodName) {
69
+ if (!import_constants.ts.isFunctionTypeNode(typeNode) || typeNode.typeParameters?.length) {
70
+ throw new Error(`Unsupported callback signature in ${methodName}().`);
71
+ }
72
+ if (typeNode.type.kind !== import_constants.ts.SyntaxKind.VoidKeyword || typeNode.parameters.length > 4) {
73
+ throw new Error(`Callbacks in ${methodName}() must return void and have at most four parameters.`);
74
+ }
75
+ const params = typeNode.parameters.map((parameter) => {
76
+ if (!import_constants.ts.isIdentifier(parameter.name) || parameter.questionToken || parameter.dotDotDotToken || !parameter.type) {
77
+ throw new Error(`Callback parameters in ${methodName}() must be named, required, and explicitly typed.`);
78
+ }
79
+ const info = typeInfo(parameter.type, sourceFile, methodName, "callback parameter");
80
+ if (info.promise || info.kind === "void") {
81
+ throw new Error(`Unsupported callback parameter type in ${methodName}().`);
82
+ }
83
+ return { name: parameter.name.text, ...info };
84
+ });
85
+ return { kind: "callback", typescript: typeNode.getText(sourceFile), cpp: "jsi::Function", params };
86
+ }
87
+ function toSnakeCase(name) {
88
+ return name.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
89
+ }
90
+ function validateIdentifier(name, location) {
91
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name) || import_constants.rustKeywords.has(name) || import_constants.cppKeywords.has(name)) {
92
+ throw new Error(`Unsupported identifier "${name}" in ${location}; use a non-keyword identifier.`);
93
+ }
94
+ }
95
+ function parseSpec(sourceText, fileName = "NativeModule.ts") {
96
+ const sourceFile = import_constants.ts.createSourceFile(fileName, sourceText, import_constants.ts.ScriptTarget.Latest, true, import_constants.ts.ScriptKind.TS);
97
+ if (sourceFile.parseDiagnostics.length > 0) {
98
+ const diagnostic = sourceFile.parseDiagnostics[0];
99
+ throw new Error(`Could not parse ${fileName}: ${import_constants.ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n")}`);
100
+ }
101
+ const spec = sourceFile.statements.find((statement) => import_constants.ts.isInterfaceDeclaration(statement) && statement.name.text === "Spec");
102
+ if (!spec) throw new Error(`No "Spec" interface was found in ${fileName}.`);
103
+ if (spec.typeParameters?.length) throw new Error("Generic TurboModule Spec interfaces are not supported.");
104
+ if (spec.members.length === 0) throw new Error("The Spec interface must declare at least one method.");
105
+ const names = /* @__PURE__ */ new Set();
106
+ const rustNames = /* @__PURE__ */ new Set();
107
+ const methods = spec.members.map((member) => {
108
+ if (!import_constants.ts.isMethodSignature(member)) {
109
+ throw new Error("Only method signatures are supported in the Spec interface.");
110
+ }
111
+ if (!member.name || !import_constants.ts.isIdentifier(member.name) || member.questionToken || member.typeParameters?.length) {
112
+ throw new Error("Optional, computed, and generic Spec methods are not supported.");
113
+ }
114
+ const name = member.name.text;
115
+ validateIdentifier(name, "Spec method");
116
+ if (names.has(name)) throw new Error(`Overloaded method "${name}" is not supported.`);
117
+ names.add(name);
118
+ const rustName = toSnakeCase(name);
119
+ validateIdentifier(rustName, `Rust mapping for ${name}()`);
120
+ if (rustNames.has(rustName)) throw new Error(`Multiple Spec methods map to the Rust name "${rustName}".`);
121
+ rustNames.add(rustName);
122
+ const params = member.parameters.map((parameter) => {
123
+ if (!import_constants.ts.isIdentifier(parameter.name) || parameter.dotDotDotToken || parameter.questionToken || parameter.initializer) {
124
+ throw new Error(`Rest, optional, and destructured parameters are not supported in ${name}().`);
125
+ }
126
+ const paramName = parameter.name.text;
127
+ validateIdentifier(paramName, `parameter in ${name}()`);
128
+ if (!parameter.type) throw new Error(`Parameter "${paramName}" in ${name}() needs an explicit type.`);
129
+ const info = import_constants.ts.isFunctionTypeNode(parameter.type) ? callbackInfo(parameter.type, sourceFile, name) : typeInfo(parameter.type, sourceFile, name, "parameter");
130
+ return { name: paramName, ...info };
131
+ });
132
+ if (new Set(params.map((param) => param.name)).size !== params.length) {
133
+ throw new Error(`Duplicate parameter names are not supported in ${name}().`);
134
+ }
135
+ if (!member.type) throw new Error(`Method "${name}()" needs an explicit return type.`);
136
+ return {
137
+ name,
138
+ rustName,
139
+ symbol: `rnrs_${rustName}`,
140
+ params,
141
+ returnType: typeInfo(member.type, sourceFile, name, "return")
142
+ };
143
+ });
144
+ for (const method of methods) {
145
+ if (method.returnType.promise && method.params.some((param) => param.kind === "callback")) {
146
+ throw new Error(`Callbacks in Promise method ${method.name}() are not supported. Use a Codegen event emitter for asynchronous callbacks.`);
147
+ }
148
+ }
149
+ return methods;
150
+ }
151
+ // Annotate the CommonJS export names for ESM import in node:
152
+ 0 && (module.exports = {
153
+ callbackInfo,
154
+ parseSpec,
155
+ toSnakeCase,
156
+ typeInfo,
157
+ validateIdentifier
158
+ });
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
15
+ var types_exports = {};
16
+ module.exports = __toCommonJS(types_exports);
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
15
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
16
+ var codegen_exports = {};
17
+ module.exports = __toCommonJS(codegen_exports);
18
+ __reExport(codegen_exports, require("./codegen/index"), module.exports);
19
+ // Annotate the CommonJS export names for ESM import in node:
20
+ 0 && (module.exports = {
21
+ ...require("./codegen/index")
22
+ });
@@ -0,0 +1,388 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var react_native_rust_lib_exports = {};
30
+ __export(react_native_rust_lib_exports, {
31
+ build: () => build,
32
+ doctor: () => doctor,
33
+ generate: () => generate,
34
+ init: () => init,
35
+ packageRoot: () => packageRoot,
36
+ readJson: () => readJson,
37
+ run: () => run
38
+ });
39
+ module.exports = __toCommonJS(react_native_rust_lib_exports);
40
+ var import_node_fs = __toESM(require("node:fs"));
41
+ var import_node_path = __toESM(require("node:path"));
42
+ var import_node_child_process = require("node:child_process");
43
+ var import_codegen = require("./codegen");
44
+ const rustDirectory = "rust";
45
+ const iosTargets = ["aarch64-apple-ios", "aarch64-apple-ios-sim", "x86_64-apple-ios"];
46
+ const androidTargets = [
47
+ ["arm64-v8a", "aarch64-linux-android"],
48
+ ["armeabi-v7a", "armv7-linux-androideabi"],
49
+ ["x86", "i686-linux-android"],
50
+ ["x86_64", "x86_64-linux-android"]
51
+ ];
52
+ function run(command, args, options = {}) {
53
+ const result = (0, import_node_child_process.spawnSync)(command, args, {
54
+ cwd: options.cwd || process.cwd(),
55
+ encoding: "utf8",
56
+ stdio: options.inherit ? "inherit" : "pipe"
57
+ });
58
+ if (result.error) return { ok: false, message: result.error.message };
59
+ if (result.status !== 0) {
60
+ return {
61
+ ok: false,
62
+ message: (result.stderr || result.stdout || `${command} exited with ${result.status}`).trim()
63
+ };
64
+ }
65
+ return { ok: true, output: (result.stdout || "").trim() };
66
+ }
67
+ function readJson(filePath) {
68
+ try {
69
+ return JSON.parse(import_node_fs.default.readFileSync(filePath, "utf8"));
70
+ } catch (error) {
71
+ throw new Error(`Cannot read ${filePath}: ${error.message}`, { cause: error });
72
+ }
73
+ }
74
+ function packageRoot() {
75
+ const root = process.cwd();
76
+ const manifestPath = import_node_path.default.join(root, "package.json");
77
+ if (!import_node_fs.default.existsSync(manifestPath)) {
78
+ throw new Error("Run this command from the root of a React Native library.");
79
+ }
80
+ const manifest = readJson(manifestPath);
81
+ const dependencies = { ...manifest.dependencies, ...manifest.peerDependencies };
82
+ if (!dependencies["react-native"]) {
83
+ throw new Error("This package does not declare react-native as a dependency or peer dependency.");
84
+ }
85
+ const configPath = import_node_path.default.join(root, "react-native.config.js");
86
+ if (!import_node_fs.default.existsSync(configPath)) {
87
+ throw new Error("Missing react-native.config.js. Create the library with the C++ module template.");
88
+ }
89
+ const config = import_node_fs.default.readFileSync(configPath, "utf8");
90
+ const moduleMatch = config.match(/cxxModuleHeaderName\s*:\s*['"]([a-zA-Z0-9_]+)Impl['"]/);
91
+ if (!moduleMatch || !config.includes("cxxModuleCMakeListsPath")) {
92
+ throw new Error("The library is not configured as a C++ TurboModule. Choose the C++ module template.");
93
+ }
94
+ return { root, manifest, manifestPath, moduleName: moduleMatch[1] };
95
+ }
96
+ function readCrateName(rustRoot) {
97
+ const manifestPath = import_node_path.default.join(rustRoot, "Cargo.toml");
98
+ const cargoManifest = import_node_fs.default.readFileSync(manifestPath, "utf8");
99
+ const crateMatch = cargoManifest.match(/^name\s*=\s*"([a-zA-Z0-9_-]+)"/m);
100
+ if (!crateMatch) throw new Error("Could not read the Rust crate name from rust/Cargo.toml.");
101
+ return crateMatch[1];
102
+ }
103
+ function init() {
104
+ const { root, manifest, manifestPath, moduleName } = packageRoot();
105
+ const rustPath = import_node_path.default.join(root, rustDirectory);
106
+ if (import_node_fs.default.existsSync(rustPath)) {
107
+ throw new Error("The rust/ directory already exists; no files were changed.");
108
+ }
109
+ const scripts = manifest.scripts || {};
110
+ if (scripts["rust:test"] || scripts["rust:generate"]) {
111
+ throw new Error("package.json already defines a rust:test or rust:generate script; no files were changed.");
112
+ }
113
+ const projectFiles = {
114
+ cppHeader: import_node_path.default.join(root, "cpp", `${moduleName}Impl.h`),
115
+ cppSource: import_node_path.default.join(root, "cpp", `${moduleName}Impl.cpp`),
116
+ nativeSpec: import_node_path.default.join(root, "src", `Native${moduleName}.ts`),
117
+ libraryIndex: import_node_path.default.join(root, "src", "index.tsx"),
118
+ androidCmake: import_node_path.default.join(root, "android", "CMakeLists.txt")
119
+ };
120
+ const podspecs = import_node_fs.default.readdirSync(root).filter((fileName) => fileName.endsWith(".podspec"));
121
+ if (podspecs.length !== 1) throw new Error("Expected exactly one CocoaPods spec file. No files were changed.");
122
+ projectFiles.podspec = import_node_path.default.join(root, podspecs[0]);
123
+ if (Object.values(projectFiles).some((filePath) => !import_node_fs.default.existsSync(filePath))) {
124
+ throw new Error("Expected C++ library template files were not found. No files were changed.");
125
+ }
126
+ const contents = Object.fromEntries(Object.entries(projectFiles).map(([key, filePath]) => [
127
+ key,
128
+ import_node_fs.default.readFileSync(filePath, "utf8")
129
+ ]));
130
+ const targetMatch = contents.androidCmake.match(/add_library\(\s*([a-zA-Z0-9_-]+)\s+STATIC/);
131
+ if (!targetMatch || !contents.cppHeader.includes("#pragma once")) {
132
+ throw new Error("The native files do not match the supported C++ TurboModule template. No files were changed.");
133
+ }
134
+ const methods = (0, import_codegen.parseSpec)(contents.nativeSpec, import_node_path.default.relative(root, projectFiles.nativeSpec));
135
+ const crateName = (manifest.name || "react-native-rust").replace(/^@[^/]+\//, "").replace(/[^a-zA-Z0-9_]+/g, "_");
136
+ const crateDirName = /^\d/.test(crateName) ? `rust_${crateName.toLowerCase()}` : crateName.toLowerCase();
137
+ const targetName = targetMatch[1];
138
+ const linkMatch = contents.androidCmake.match(/target_link_libraries\(\s*([a-zA-Z0-9_-]+)([\s\S]*?)\n\)/);
139
+ if (!linkMatch || linkMatch[1] !== targetName) {
140
+ throw new Error("Could not locate the C++ target link block in android/CMakeLists.txt.");
141
+ }
142
+ const androidLink = `${linkMatch[0].slice(0, -2)}
143
+ rust_core
144
+ log
145
+ dl
146
+ m
147
+ )`;
148
+ const rustImport = `add_library(rust_core STATIC IMPORTED)
149
+ set_target_properties(rust_core PROPERTIES
150
+ IMPORTED_LOCATION "\${CMAKE_CURRENT_LIST_DIR}/../rust/build/android/\${ANDROID_ABI}/lib${crateDirName}.a"
151
+ )
152
+
153
+ `;
154
+ const androidCmake = contents.androidCmake.replace(linkMatch[0], androidLink).replace(/add_library\(/, `${rustImport}add_library(`);
155
+ const podspecSource = contents.podspec.match(/^\s*s\.source_files\s*=.*$/m);
156
+ if (!podspecSource) throw new Error("Could not locate source_files in the CocoaPods spec.");
157
+ const podspec = contents.podspec.replace(
158
+ podspecSource[0],
159
+ `${podspecSource[0]}
160
+ s.vendored_frameworks = "rust/build/ios/${moduleName}Rust.xcframework"`
161
+ );
162
+ const rustFiles = {
163
+ "Cargo.toml": `[package]
164
+ name = "${crateDirName}"
165
+ version = "0.1.0"
166
+ edition = "2021"
167
+
168
+ [lib]
169
+ crate-type = ["staticlib", "cdylib"]
170
+
171
+ [dependencies]
172
+ serde = { version = "1", features = ["derive"] }
173
+ serde_json = "1"
174
+
175
+ [target.'cfg(target_arch = "wasm32")'.dependencies]
176
+ wasm-bindgen = "0.2"
177
+ js-sys = "0.3"
178
+
179
+ [build-dependencies]
180
+ cbindgen = "0.26"
181
+ `,
182
+ "build.rs": `use std::path::PathBuf;
183
+
184
+ fn main() {
185
+ let crate_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
186
+ let output = PathBuf::from(&crate_dir).join("include/rust_api.h");
187
+ std::fs::create_dir_all(output.parent().unwrap()).unwrap();
188
+ cbindgen::generate(&crate_dir)
189
+ .expect("failed to generate the Rust C header")
190
+ .write_to_file(output);
191
+ }
192
+ `,
193
+ "cbindgen.toml": `language = "C++"
194
+ `,
195
+ "src/ffi.rs": (0, import_codegen.renderRustFfiModule)(),
196
+ "README.md": `# Rust core
197
+
198
+ Rust handlers are generated from the TurboModule Spec interface in src/Native${moduleName}.ts. Edit the generated functions under rust/src/api/ and regenerate native glue with npx react-native-rust generate.
199
+
200
+ Run cargo test --manifest-path rust/Cargo.toml for Rust tests. Build iOS and Android artifacts with npx react-native-rust build ios and npx react-native-rust build android before native app builds. Build the react-native-web WebAssembly package with npx react-native-rust build web (requires wasm-pack and the wasm32-unknown-unknown Rust target).
201
+ `
202
+ };
203
+ const updates = (0, import_codegen.renderProjectBindings)(root, moduleName, methods, true, crateDirName);
204
+ updates.set(projectFiles.androidCmake, androidCmake);
205
+ updates.set(projectFiles.podspec, podspec);
206
+ const cliManifest = readJson(import_node_path.default.join(__dirname, "..", "package.json"));
207
+ const cliRoot = import_node_path.default.resolve(__dirname, "..");
208
+ const cliIsInstalled = cliRoot.split(import_node_path.default.sep).includes("node_modules");
209
+ const cliDependency = cliIsInstalled ? `^${cliManifest.version}` : `file:${import_node_path.default.relative(root, cliRoot).split(import_node_path.default.sep).join("/") || "."}`;
210
+ const updatedManifest = {
211
+ ...manifest,
212
+ devDependencies: {
213
+ ...manifest.devDependencies,
214
+ [cliManifest.name]: manifest.devDependencies?.[cliManifest.name] || cliDependency
215
+ },
216
+ scripts: {
217
+ ...scripts,
218
+ "rust:test": "cargo test --manifest-path rust/Cargo.toml",
219
+ "rust:generate": "react-native-rust generate",
220
+ "rust:build:ios": "react-native-rust build ios",
221
+ "rust:build:android": "react-native-rust build android",
222
+ "rust:build:web": "react-native-rust build web"
223
+ }
224
+ };
225
+ const packageFiles = Array.isArray(updatedManifest.files) ? [...updatedManifest.files] : [];
226
+ for (const filePath of ["rust/", "rust/build/", "rust/include/"]) {
227
+ if (!packageFiles.includes(filePath)) packageFiles.push(filePath);
228
+ }
229
+ updatedManifest.files = packageFiles;
230
+ updates.set(manifestPath, `${JSON.stringify(updatedManifest, null, 2)}
231
+ `);
232
+ const gitignorePath = import_node_path.default.join(root, ".gitignore");
233
+ const gitignore = import_node_fs.default.existsSync(gitignorePath) ? import_node_fs.default.readFileSync(gitignorePath, "utf8") : "";
234
+ const ignoredRustOutputs = ["rust/target/", "rust/build/"].filter((entry) => !gitignore.split(/\r?\n/).includes(entry));
235
+ if (ignoredRustOutputs.length > 0) {
236
+ const separator = gitignore.length > 0 && !gitignore.endsWith("\n") ? "\n" : "";
237
+ updates.set(gitignorePath, `${gitignore}${separator}${ignoredRustOutputs.join("\n")}
238
+ `);
239
+ }
240
+ for (const [relativePath, fileContents] of Object.entries(rustFiles)) {
241
+ const outputPath = import_node_path.default.join(rustPath, relativePath);
242
+ import_node_fs.default.mkdirSync(import_node_path.default.dirname(outputPath), { recursive: true });
243
+ import_node_fs.default.writeFileSync(outputPath, fileContents);
244
+ }
245
+ for (const [filePath, fileContents] of updates) {
246
+ import_node_fs.default.mkdirSync(import_node_path.default.dirname(filePath), { recursive: true });
247
+ import_node_fs.default.writeFileSync(filePath, fileContents);
248
+ }
249
+ console.log(`Generated ${methods.length} Rust handler(s) and native bindings from Spec.`);
250
+ console.log("Run `npx react-native-rust build ios` or `npx react-native-rust build android` before building the React Native app.");
251
+ }
252
+ function generate() {
253
+ const { root, moduleName } = packageRoot();
254
+ const rustPath = import_node_path.default.join(root, rustDirectory);
255
+ if (!import_node_fs.default.existsSync(import_node_path.default.join(rustPath, "Cargo.toml"))) {
256
+ throw new Error("Rust is not initialized. Run `npx react-native-rust init` first.");
257
+ }
258
+ const nativeSpecPath = import_node_path.default.join(root, "src", `Native${moduleName}.ts`);
259
+ const methods = (0, import_codegen.parseSpec)(import_node_fs.default.readFileSync(nativeSpecPath, "utf8"), import_node_path.default.relative(root, nativeSpecPath));
260
+ const updates = (0, import_codegen.renderProjectBindings)(root, moduleName, methods, false, readCrateName(rustPath));
261
+ for (const [filePath, fileContents] of updates) {
262
+ import_node_fs.default.mkdirSync(import_node_path.default.dirname(filePath), { recursive: true });
263
+ import_node_fs.default.writeFileSync(filePath, fileContents);
264
+ }
265
+ console.log(`Regenerated Rust, C++, and TypeScript glue for ${methods.length} Spec method(s).`);
266
+ }
267
+ function doctor(target) {
268
+ const { root } = packageRoot();
269
+ if (!import_node_fs.default.existsSync(import_node_path.default.join(root, rustDirectory, "Cargo.toml"))) {
270
+ throw new Error("Rust is not initialized. Run `npx react-native-rust init` first.");
271
+ }
272
+ let failed = false;
273
+ for (const command of ["rustc", "cargo"]) {
274
+ const result = run(command, ["--version"]);
275
+ console.log(`${result.ok ? "OK" : "MISSING"} ${command}${result.output ? `: ${result.output}` : ""}`);
276
+ failed ||= !result.ok;
277
+ }
278
+ if (target === "ios") {
279
+ const xcode = run("xcodebuild", ["-version"]);
280
+ const lipo = run("lipo", ["-version"]);
281
+ console.log(`${xcode.ok ? "OK" : "MISSING"} Xcode`);
282
+ console.log(`${lipo.ok ? "OK" : "MISSING"} lipo`);
283
+ failed ||= process.platform !== "darwin" || !xcode.ok || !lipo.ok;
284
+ }
285
+ if (target === "android") {
286
+ const ndkPath = process.env.ANDROID_NDK_HOME || process.env.ANDROID_NDK_ROOT;
287
+ const cargoNdk = run("cargo", ["ndk", "--version"]);
288
+ console.log(`${ndkPath ? "OK" : "CHECK"} Android NDK path (cargo-ndk can auto-detect Android Studio installs)`);
289
+ console.log(`${cargoNdk.ok ? "OK" : "MISSING"} cargo-ndk`);
290
+ failed ||= !cargoNdk.ok;
291
+ }
292
+ if (target === "web") {
293
+ const wasmPack = run("wasm-pack", ["--version"]);
294
+ const installedTargets = run("rustup", ["target", "list", "--installed"]);
295
+ const hasWasmTarget = installedTargets.ok && (installedTargets.output || "").includes("wasm32-unknown-unknown");
296
+ console.log(`${wasmPack.ok ? "OK" : "MISSING"} wasm-pack`);
297
+ console.log(`${hasWasmTarget ? "OK" : "MISSING"} wasm32-unknown-unknown Rust target (rustup target add wasm32-unknown-unknown)`);
298
+ failed ||= !wasmPack.ok || !hasWasmTarget;
299
+ }
300
+ if (failed) process.exitCode = 1;
301
+ }
302
+ function build(platform) {
303
+ const { root, manifest, moduleName } = packageRoot();
304
+ const rustRoot = import_node_path.default.join(root, rustDirectory);
305
+ const manifestPath = import_node_path.default.join(rustRoot, "Cargo.toml");
306
+ if (!import_node_fs.default.existsSync(manifestPath)) {
307
+ throw new Error("Rust is not initialized. Run `npx react-native-rust init` first.");
308
+ }
309
+ const crateName = readCrateName(rustRoot);
310
+ const releasePath = (target) => import_node_path.default.join(rustRoot, "target", target, "release", `lib${crateName}.a`);
311
+ if (platform === "ios") {
312
+ for (const target of iosTargets) {
313
+ const result = run("cargo", ["build", "--release", "--target", target, "--manifest-path", manifestPath], { inherit: true });
314
+ if (!result.ok) throw new Error(`iOS build failed for ${target}: ${result.message}`);
315
+ }
316
+ const buildPath = import_node_path.default.join(rustRoot, "build", "ios");
317
+ import_node_fs.default.mkdirSync(buildPath, { recursive: true });
318
+ const simulatorDirectory = import_node_path.default.join(buildPath, "simulator");
319
+ import_node_fs.default.mkdirSync(simulatorDirectory, { recursive: true });
320
+ const simulatorArchive = import_node_path.default.join(simulatorDirectory, `lib${crateName}.a`);
321
+ const lipo = run("lipo", [
322
+ "-create",
323
+ releasePath("aarch64-apple-ios-sim"),
324
+ releasePath("x86_64-apple-ios"),
325
+ "-output",
326
+ simulatorArchive
327
+ ], { inherit: true });
328
+ if (!lipo.ok) throw new Error(`Could not combine iOS simulator archives: ${lipo.message}`);
329
+ const frameworkPath = import_node_path.default.join(buildPath, `${moduleName}Rust.xcframework`);
330
+ import_node_fs.default.rmSync(frameworkPath, { recursive: true, force: true });
331
+ const xcframework = run("xcodebuild", [
332
+ "-create-xcframework",
333
+ "-library",
334
+ releasePath("aarch64-apple-ios"),
335
+ "-headers",
336
+ import_node_path.default.join(rustRoot, "include"),
337
+ "-library",
338
+ simulatorArchive,
339
+ "-headers",
340
+ import_node_path.default.join(rustRoot, "include"),
341
+ "-output",
342
+ frameworkPath
343
+ ], { inherit: true });
344
+ if (!xcframework.ok) throw new Error(`Could not create the iOS XCFramework: ${xcframework.message}`);
345
+ console.log(`Built ${import_node_path.default.relative(root, frameworkPath)} for ${manifest.name}.`);
346
+ return;
347
+ }
348
+ if (platform === "android") {
349
+ const result = run("cargo", [
350
+ "ndk",
351
+ "-t",
352
+ "arm64-v8a",
353
+ "-t",
354
+ "armeabi-v7a",
355
+ "-t",
356
+ "x86",
357
+ "-t",
358
+ "x86_64",
359
+ "build",
360
+ "--release"
361
+ ], { inherit: true, cwd: rustRoot });
362
+ if (!result.ok) throw new Error(`Android build failed: ${result.message}`);
363
+ for (const [abi, target] of androidTargets) {
364
+ const outputDirectory = import_node_path.default.join(rustRoot, "build", "android", abi);
365
+ import_node_fs.default.mkdirSync(outputDirectory, { recursive: true });
366
+ import_node_fs.default.copyFileSync(releasePath(target), import_node_path.default.join(outputDirectory, `lib${crateName}.a`));
367
+ }
368
+ console.log(`Built Android static archives for ${manifest.name}.`);
369
+ return;
370
+ }
371
+ if (platform === "web") {
372
+ const result = run("wasm-pack", ["build", "--target", "web", "--out-dir", import_node_path.default.join("build", "web", "pkg")], { inherit: true, cwd: rustRoot });
373
+ if (!result.ok) throw new Error(`Web build failed: ${result.message}`);
374
+ console.log(`Built rust/build/web/pkg for ${manifest.name}.`);
375
+ return;
376
+ }
377
+ throw new Error("Choose a build target: ios, android, or web.");
378
+ }
379
+ // Annotate the CommonJS export names for ESM import in node:
380
+ 0 && (module.exports = {
381
+ build,
382
+ doctor,
383
+ generate,
384
+ init,
385
+ packageRoot,
386
+ readJson,
387
+ run
388
+ });