Skip to content
This repository was archived by the owner on May 30, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,25 @@ export async function loadPlatformModule() {
}
}

// Graceful fallback for optional dependencies
export function useFSEvents() {
// Graceful fallback for optional dependencies. Uses dynamic `import()` so the
// example works under ESM (`"type": "module"`) and degrades to native
// `fs.watch` when neither `fsevents` nor `chokidar` is installed.
export async function useFSEvents() {
try {
// fsevents is macOS only
if (process.platform === "darwin") {
const fsevents = require("fsevents");
return fsevents;
return await import("fsevents");
}
} catch (error) {
console.warn("fsevents not available, using fallback");
}

// Fallback to chokidar or fs.watch
return require("chokidar");
// Fallback to chokidar, then to native fs.watch as a final safety net.
try {
return await import("chokidar");
} catch {
const { watch } = await import("node:fs/promises");
return { watch };
}
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,34 @@ const execFileAsync = promisify(execFile);

export class ProcessUtils {
// Kill process by PID with platform-specific signal
static kill(pid: number, signal?: string): void {
static async kill(pid: number, signal?: string): Promise<void> {
if (process.platform === "win32") {
// Windows doesn't support signals, use taskkill
spawn("taskkill", ["/pid", pid.toString(), "/f", "/t"]);
} else {
process.kill(pid, signal || "SIGTERM");
// Windows doesn't support POSIX signals. Await taskkill so spawn
// failures and non-zero exits reject this Promise and remain catchable.
await execFileAsync("taskkill", ["/pid", pid.toString(), "/f", "/t"]);
return;
}

process.kill(pid, signal ?? "SIGTERM");
}

// Spawn process with platform-specific handling
static spawnCommand(command: string, args: string[] = []): ChildProcess {
if (process.platform === "win32") {
// Windows requires cmd.exe to run commands
// Windows requires cmd.exe to run commands. shell: false (the default)
// is mandatory whenever an argv array is used — Node emits DEP0190 and
// arguments are concatenated without shell escaping when shell: true is
// combined with args, which is an injection vector.
return spawn("cmd", ["/c", command, ...args], {
stdio: "inherit",
shell: true,
});
}

// shell: false (the default) — args are passed directly to execvp without
// shell interpretation. If shell semantics are required (e.g. globbing),
// build a properly escaped command string and pass it as a single argv[0].
return spawn(command, args, {
stdio: "inherit",
shell: true,
});
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@

```typescript
// shell-utils.ts
import { exec } from "child_process";
import { exec, execFile } from "child_process";
import { readdir } from "fs/promises";
import { promisify } from "util";

const execAsync = promisify(exec);
const execFileAsync = promisify(execFile);

export class ShellUtils {
// Execute command with platform-specific handling
// WARNING: Runs `command` through a shell. NEVER pass untrusted input here —
// shell metacharacters (`$()`, backticks, `;`, `|`, `&`, redirections) will
// be interpreted. For any value derived from user input, prefer
// `executeSafe` below, which uses execFile and does not invoke a shell.
static async execute(command: string): Promise<string> {
try {
const { stdout, stderr } = await execAsync(command, {
Expand All @@ -19,10 +24,17 @@ export class ShellUtils {
if (stderr) console.error(stderr);
return stdout.trim();
} catch (error) {
throw new Error(`Command failed: ${error.message}`);
throw new Error(`Command failed: ${(error as Error).message}`);
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Safe execution with argv array — no shell interpretation. Use this for
// any path or value that may originate outside the program.
static async executeSafe(command: string, args: string[] = []): Promise<string> {
const { stdout, stderr } = await execFileAsync(command, args);
if (stderr) console.error(stderr);
return stdout.trim();
}
// Get platform-specific shell
static getShell(): string {
if (process.platform === "win32") {
Expand All @@ -31,29 +43,25 @@ export class ShellUtils {
return process.env.SHELL || "/bin/sh";
}

// Platform-specific commands
// Prefer runtime APIs for filesystem work. Shell builtins like `dir` must
// go through cmd.exe, where caller-supplied paths are shell-parsed unless
// explicitly escaped.
static async listFiles(directory: string): Promise<string> {
if (process.platform === "win32") {
return this.execute(`dir "${directory}"`);
}
return this.execute(`ls -la "${directory}"`);
const entries = await readdir(directory, { withFileTypes: true });
return entries.map((entry) => entry.name).join("\n");
}

static async clearScreen(): Promise<void> {
if (process.platform === "win32") {
await this.execute("cls");
} else {
await this.execute("clear");
}
process.stdout.write("\x1Bc");
}

static async openFile(filepath: string): Promise<void> {
if (process.platform === "win32") {
await this.execute(`start "" "${filepath}"`);
await execFileAsync("explorer.exe", [filepath]);
} else if (process.platform === "darwin") {
await this.execute(`open "${filepath}"`);
await execFileAsync("open", [filepath]);
} else {
await this.execute(`xdg-open "${filepath}"`);
await execFileAsync("xdg-open", [filepath]);
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
Expand Down
6 changes: 3 additions & 3 deletions .omp/omp-audit-config.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"lastAnalyzedVersion": "15.1.3",
"supipowersVersionAtLastAudit": "2.2.0",
"lastAuditDate": "2026-05-17",
"lastAnalyzedVersion": "15.1.7",
"supipowersVersionAtLastAudit": "2.2.2",
"lastAuditDate": "2026-05-19",
"auditOutputFile": "OMP_CHANGELOG_AUDIT.md"
}
Loading
Loading