React Native macOS child process module
npm install rn-macos-child-processA native macOS terminal/command execution module for React Native macOS.
Features:
* š¢ Run shell commands (e.g., npm -v, ls, php artisan, composer)
* š¢ Real-time stdout + stderr streaming
* š¢ Reject automatically when stderr appears
* š¢ Kill running processes
* š¢ List active processes
* š¢ Change directory / read environment / system info
* š¢ Fully typed (TypeScript declarations included)
* š¢ Uses Swift + FileHandle streaming (non-blocking)
---
```
npm install rn-macos-child-process
Or:
``
yarn add rn-macos-child-process
---
Nothing else required.
If for some reason the macOS project did not update:
``
cd macos
pod install
---
`ts`
import Process from "rn-macos-child-process";
---
These are included automatically through your index.d.ts.
`ts
export type ExecResult = {
pid: number;
code: number;
stdout: string;
stderr: string;
cwd?: string | null;
};
export type ProcessEvent =
| "process-stdout"
| "process-stderr"
| "process-exit"
| "process-start"
| "process-error";
`
---
Runs a command and returns a final result (no options object).
`ts`
const result = await Process.executeCommand("npm", ["-v"]);
---
Same as above but provides more configuration:
`ts`
const result = await Process.executeWithOptions("ls", ["-la"], {
cwd: "/Users/me",
env: { CUSTOM_VAR: "1", envPaths: ['/Users/username/Library/Application Support/Herd/config/nvm/versions/node/v22.18.0/bin', 'another'] }
});
Returns ExecResult:
`ts`
{
pid: 12345,
code: 0,
stdout: "...",
stderr: "",
cwd: "/Users/me"
}
---
`ts`
await Process.killProcess(12345, 15);
---
Returns all active PIDs:
`ts`
const running = await Process.listRunning();
---
Changes internal working directory:
`ts`
await Process.changeDirectory("/Users/me/project");
---
`ts`
const cwd = await Process.getCurrentDirectory();
---
`ts`
const env = await Process.getEnvironment();
---
Returns CPU, RAM, OS, and more:
`ts`
const info = await Process.getSystemInfo();
---
Use:
`ts`
const sub = Process.addListener("process-stdout", event => {
console.log(event.data);
});
Supported events:
| Event | Description |
| ---------------- | --------------------- |
| process-start | When command begins |process-stdout
| | When stdout emits |process-stderr
| | When stderr emits |process-exit
| | When the process ends |process-error
| | Internal native error |
Remove listeners:
`ts`
Process.removeAllListeners("process-stdout");
---
`tsx
import React, {useEffect, useState} from "react";
import {View, Button, Text, ScrollView} from "react-native";
import Process from "rn-macos-child-process";
export default function TerminalScreen() {
const [logs, setLogs] = useState
const [runningPid, setRunningPid] = useState
useEffect(() => {
const subs = [
Process.addListener("process-start", e =>
push([START] pid=${e.pid} cwd=${e.cwd})[OUT] ${e.data}
),
Process.addListener("process-stdout", e =>
push()[ERR] ${e.data}
),
Process.addListener("process-stderr", e =>
push()[EXIT] code=${e.code}
),
Process.addListener("process-exit", e =>
push()[NATIVE ERROR] ${e.message}
),
Process.addListener("process-error", e =>
push()
),
];
return () => subs.forEach(s => s.remove());
}, []);
const push = (msg: string) =>
setLogs(prev => [...prev, msg]);
const run = async () => {
setLogs([]);
const result = await Process.executeWithOptions(
"ls",
["-la"],
{cwd: "/Users/alfrednti"}
);
setRunningPid(result.pid);
};
return (
{logs.map((l, i) => (
{l}
))}
);
}
`
---
The native module rejects on stderr automatically:
If stderr emits first*, the process stops
* process-stderr event still fires (so UI can show it)
* Promise rejects with:
`ts`
{
code: "STDERR_ERROR",
message: "...",
nativeStackIOS: [...]
}
This ensures:
ā safe behavior
ā correct JS error stack
ā consistent error mapping
---
``
react-native-macos-process/
ā
āāā index.js
āāā index.d.ts
āāā package.json
āāā README.md
ā
āāā macos/
ā āāā ProcessModule.swift
ā āāā ProcessModule.m
ā
āāā react-native.config.js
---
`ts``
Process.executeCommand("npm", ["-v"]);
Process.executeCommand("composer", ["install"]);
Process.executeCommand("php", ["artisan", "migrate"]);
Process.executeCommand("git", ["status"]);
Process.executeWithOptions("bash", ["script.sh"], {cwd: "/scripts"});
---
If you need:
* full rewrite in Objective-C instead of Swift
* additional events
* persistent background processes
* streaming chunk size changes
* command queue system
* sandbox bypass (within macOS rules)
Just open an issue or request an enhancement.