
Cross Platform Compatibility
- 430 installs
- 305 repo stars
- Updated March 4, 2026
- aj-geddes/useful-ai-prompts
cross-platform-compatibility is an agent skill that audits applications for Windows, macOS, Linux, iOS, and Android path, shell, API, and build-target compatibility before release.
About
cross-platform-compatibility is an aj-geddes/useful-ai-prompts agent skill for writing and auditing code that works across Windows, macOS, and Linux desktop environments plus mobile considerations. It covers platform-independent path construction with Node path and os modules, environment and architecture detection, platform-specific dependencies, shell command portability, line endings, file permissions, process management, character encoding, and cross-platform build configuration. The skill ships quick-start TypeScript examples contrasting hardcoded C:\ and /home paths with path.join and path.resolve patterns, plus 11 reference guides in references/ including file-path-handling, platform-detection, shell-commands, and testing-across-platforms. Developers reach for cross-platform-compatibility when building CLI tools, desktop apps, or release pipelines that must not break on path separators, shells, or OS-specific APIs across target platforms.
- Multi-OS checklist
- Path and shell differences
- Native API gap analysis
- CI matrix recommendations
Cross Platform Compatibility by the numbers
- 430 all-time installs (skills.sh)
- Ranked #638 of 2,153 Testing & QA skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/aj-geddes/useful-ai-prompts --skill cross-platform-compatibilityAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 430 |
|---|---|
| repo stars | ★ 305 |
| Last updated | March 4, 2026 |
| Repository | aj-geddes/useful-ai-prompts ↗ |
How do you write cross-platform file paths in Node?
Audit apps for Windows, macOS, Linux, iOS, and Android compatibility—paths, shells, native APIs, fonts, and build targets—before release.
Who is it for?
Developers shipping CLI tools or Node.js apps that must run on Windows, macOS, and Linux without hardcoded paths or shell assumptions.
Skip if: Single-platform iOS-only or web-only projects with no OS-specific filesystem or shell concerns.
When should I use this skill?
Code uses hardcoded paths, platform-specific shell commands, or must be validated across Windows, macOS, Linux, iOS, and Android targets before release.
What you get
Platform-safe path patterns, OS detection snippets, shell portability fixes, and reference guide pointers for encoding and build config.
- Portable path and shell patterns
- Platform detection snippets
- Compatibility audit checklist
By the numbers
- Includes 11 reference guides in the references/ directory
Files
Cross-Platform Compatibility
Table of Contents
Overview
Comprehensive guide to writing code that works seamlessly across Windows, macOS, and Linux. Covers file path handling, environment detection, platform-specific features, and testing strategies.
When to Use
- Building applications for multiple operating systems
- Handling file system operations
- Managing platform-specific dependencies
- Detecting operating system and architecture
- Working with environment variables
- Building cross-platform CLI tools
- Dealing with line endings and character encodings
- Managing platform-specific build processes
Quick Start
Minimal working example:
// ❌ BAD: Hardcoded paths with platform-specific separators
const configPath = "C:\\Users\\user\\config.json"; // Windows only
const dataPath = "/home/user/data.txt"; // Unix only
// ✅ GOOD: Use path module
import path from "path";
import os from "os";
// Platform-independent path construction
const configPath = path.join(os.homedir(), "config", "app.json");
const dataPath = path.join(process.cwd(), "data", "users.txt");
// Resolve relative paths
const absolutePath = path.resolve("./config/settings.json");
// Get path components
const dirname = path.dirname("/path/to/file.txt"); // '/path/to'
const basename = path.basename("/path/to/file.txt"); // 'file.txt'
const extname = path.extname("/path/to/file.txt"); // '.txt'
// Normalize paths (handle .. and .)
const normalized = path.normalize("/path/to/../file.txt"); // '/path/file.txt'Reference Guides
Detailed implementations in the references/ directory:
| Guide | Contents |
|---|---|
| File Path Handling | File Path Handling |
| Platform Detection | Platform Detection |
| Line Endings | Line Endings |
| Environment Variables | Environment Variables |
| Shell Commands | Shell Commands |
| File Permissions | File Permissions |
| Process Management | Process Management |
| Platform-Specific Dependencies | Platform-Specific Dependencies |
| Testing Across Platforms | Testing Across Platforms |
| Character Encoding | Character Encoding |
| Build Configuration | Build Configuration |
Best Practices
✅ DO
- Use path.join() or path.resolve() for paths
- Use os.EOL for line endings
- Detect platform at runtime when needed
- Test on all target platforms
- Use optionalDependencies for platform-specific modules
- Handle file permissions gracefully
- Use shell escaping for user input
- Normalize line endings in text files
- Use UTF-8 encoding by default
- Document platform-specific behavior
- Provide fallbacks for platform-specific features
- Use CI/CD to test on multiple platforms
❌ DON'T
- Hardcode file paths with backslashes or forward slashes
- Assume Unix-only features (signals, permissions, symlinks)
- Ignore Windows-specific quirks (drive letters, UNC paths)
- Use platform-specific commands without fallbacks
- Assume case-sensitive file systems
- Forget about different line endings
- Use platform-specific APIs without checking
- Hardcode environment variable access patterns
- Ignore character encoding issues
Build Configuration
Build Configuration
// rollup.config.js
export default {
input: "src/index.ts",
output: [
{
file: "dist/index.js",
format: "cjs",
},
{
file: "dist/index.esm.js",
format: "esm",
},
],
external: [
// Mark platform-specific modules as external
"fsevents",
],
plugins: [
// Replace platform checks at build time for better tree-shaking
replace({
"process.platform": JSON.stringify(process.platform),
preventAssignment: true,
}),
],
};Character Encoding
Character Encoding
// encoding-utils.ts
import iconv from "iconv-lite";
export class EncodingUtils {
// Read file with specific encoding
static readFile(filepath: string, encoding: string = "utf8"): string {
const buffer = fs.readFileSync(filepath);
if (encoding === "utf8") {
// Remove BOM if present
if (buffer[0] === 0xef && buffer[1] === 0xbb && buffer[2] === 0xbf) {
return buffer.slice(3).toString("utf8");
}
return buffer.toString("utf8");
}
return iconv.decode(buffer, encoding);
}
// Write file with specific encoding
static writeFile(
filepath: string,
content: string,
encoding: string = "utf8",
): void {
if (encoding === "utf8") {
fs.writeFileSync(filepath, content, "utf8");
} else {
const buffer = iconv.encode(content, encoding);
fs.writeFileSync(filepath, buffer);
}
}
// Detect encoding
static detectEncoding(filepath: string): string {
const buffer = fs.readFileSync(filepath);
// Check for BOM
if (buffer[0] === 0xef && buffer[1] === 0xbb && buffer[2] === 0xbf) {
return "utf8";
}
if (buffer[0] === 0xfe && buffer[1] === 0xff) {
return "utf16be";
}
if (buffer[0] === 0xff && buffer[1] === 0xfe) {
return "utf16le";
}
// Default to UTF-8
return "utf8";
}
}Environment Variables
Environment Variables
// env-utils.ts
export class EnvUtils {
// Get environment variable with fallback
static get(key: string, defaultValue?: string): string | undefined {
return process.env[key] || defaultValue;
}
// Get PATH separator (: on Unix, ; on Windows)
static get pathSeparator(): string {
return process.platform === "win32" ? ";" : ":";
}
// Split PATH into array
static getPaths(): string[] {
const pathVar = process.env.PATH || "";
return pathVar.split(this.pathSeparator);
}
// Get common paths
static get home(): string {
return process.env.HOME || process.env.USERPROFILE || "";
}
static get user(): string {
return process.env.USER || process.env.USERNAME || "";
}
// Check if running in CI
static get isCI(): boolean {
return !!(
process.env.CI ||
process.env.CONTINUOUS_INTEGRATION ||
process.env.GITHUB_ACTIONS ||
process.env.GITLAB_CI
);
}
}File Path Handling
File Path Handling
Node.js Path Module
// ❌ BAD: Hardcoded paths with platform-specific separators
const configPath = "C:\\Users\\user\\config.json"; // Windows only
const dataPath = "/home/user/data.txt"; // Unix only
// ✅ GOOD: Use path module
import path from "path";
import os from "os";
// Platform-independent path construction
const configPath = path.join(os.homedir(), "config", "app.json");
const dataPath = path.join(process.cwd(), "data", "users.txt");
// Resolve relative paths
const absolutePath = path.resolve("./config/settings.json");
// Get path components
const dirname = path.dirname("/path/to/file.txt"); // '/path/to'
const basename = path.basename("/path/to/file.txt"); // 'file.txt'
const extname = path.extname("/path/to/file.txt"); // '.txt'
// Normalize paths (handle .. and .)
const normalized = path.normalize("/path/to/../file.txt"); // '/path/file.txt'Python Path Handling
# ❌ BAD: Hardcoded separators
config_path = 'C:\\Users\\user\\config.json' # Windows only
data_path = '/home/user/data.txt' # Unix only
# ✅ GOOD: Use pathlib
from pathlib import Path
import os
# Platform-independent path construction
config_path = Path.home() / 'config' / 'app.json'
data_path = Path.cwd() / 'data' / 'users.txt'
# Working with paths
if config_path.exists():
content = config_path.read_text()
# Get path components
dirname = config_path.parent
filename = config_path.name
extension = config_path.suffix
# Resolve relative paths
absolute_path = Path('./config/settings.json').resolve()
# Create directories
output_dir = Path('output')
output_dir.mkdir(parents=True, exist_ok=True)Go Path Handling
package main
import (
"os"
"path/filepath"
)
func main() {
// ❌ BAD: Hardcoded paths
// configPath := "C:\\Users\\user\\config.json"
// ✅ GOOD: Use filepath package
homeDir, _ := os.UserHomeDir()
configPath := filepath.Join(homeDir, "config", "app.json")
// Get path components
dir := filepath.Dir(configPath)
base := filepath.Base(configPath)
ext := filepath.Ext(configPath)
// Clean and normalize paths
cleaned := filepath.Clean("path/to/../file.txt")
// Convert to absolute path
absPath, _ := filepath.Abs("./config/settings.json")
}File Permissions
File Permissions
// permissions.ts
import fs from "fs";
import path from "path";
export class FilePermissions {
// Make file executable (Unix only)
static makeExecutable(filepath: string): void {
if (process.platform !== "win32") {
fs.chmodSync(filepath, 0o755);
}
}
// Check if file is executable
static isExecutable(filepath: string): boolean {
if (process.platform === "win32") {
// On Windows, check file extension
const ext = path.extname(filepath).toLowerCase();
return [".exe", ".bat", ".cmd", ".com"].includes(ext);
}
try {
fs.accessSync(filepath, fs.constants.X_OK);
return true;
} catch {
return false;
}
}
// Create file with specific permissions (Unix)
static createWithPermissions(
filepath: string,
content: string,
mode: number = 0o644,
): void {
fs.writeFileSync(filepath, content, { mode });
}
}Line Endings
Line Endings
// line-endings.ts
import os from "os";
export const LineEnding = {
LF: "\n", // Unix/Linux/macOS
CRLF: "\r\n", // Windows
CR: "\r", // Old Mac (pre-OS X)
get platform(): string {
return os.EOL; // Returns platform-specific line ending
},
normalize(text: string, target: string = os.EOL): string {
// Normalize all line endings to target
return text.replace(/\r\n|\r|\n/g, target);
},
toUnix(text: string): string {
return this.normalize(text, this.LF);
},
toWindows(text: string): string {
return this.normalize(text, this.CRLF);
},
};
// Usage
const fileContent = fs.readFileSync("file.txt", "utf8");
// Normalize to platform-specific line endings
const normalized = LineEnding.normalize(fileContent);
// Force Unix line endings (for git, etc.)
const unixContent = LineEnding.toUnix(fileContent);
// Write with platform-specific line endings
fs.writeFileSync("output.txt", normalized);Platform Detection
Platform Detection
Node.js Platform Detection
// platform-utils.ts
import os from "os";
export const Platform = {
isWindows: process.platform === "win32",
isMacOS: process.platform === "darwin",
isLinux: process.platform === "linux",
isUnix: process.platform !== "win32",
get current(): "windows" | "macos" | "linux" | "unknown" {
switch (process.platform) {
case "win32":
return "windows";
case "darwin":
return "macos";
case "linux":
return "linux";
default:
return "unknown";
}
},
get arch(): string {
return process.arch; // 'x64', 'arm64', etc.
},
get homeDir(): string {
return os.homedir();
},
get tempDir(): string {
return os.tmpdir();
},
};
// Usage
if (Platform.isWindows) {
// Windows-specific code
console.log("Running on Windows");
} else if (Platform.isMacOS) {
// macOS-specific code
console.log("Running on macOS");
} else if (Platform.isLinux) {
// Linux-specific code
console.log("Running on Linux");
}
// Architecture detection
if (Platform.arch === "arm64") {
console.log("Running on ARM architecture");
}Python Platform Detection
# platform_utils.py
import platform
import sys
class Platform:
@staticmethod
def is_windows():
return sys.platform.startswith('win')
@staticmethod
def is_macos():
return sys.platform == 'darwin'
@staticmethod
def is_linux():
return sys.platform.startswith('linux')
@staticmethod
def is_unix():
return not Platform.is_windows()
@staticmethod
def current():
if Platform.is_windows():
return 'windows'
elif Platform.is_macos():
return 'macos'
elif Platform.is_linux():
return 'linux'
return 'unknown'
@staticmethod
def arch():
return platform.machine() # 'x86_64', 'arm64', etc.
@staticmethod
def version():
return platform.version()
# Usage
if Platform.is_windows():
# Windows-specific code
print('Running on Windows')
elif Platform.is_macos():
# macOS-specific code
print('Running on macOS')
elif Platform.is_linux():
# Linux-specific code
print('Running on Linux')Platform-Specific Dependencies
Platform-Specific Dependencies
// package.json
{
"name": "my-app",
"dependencies": {
"common-dep": "^1.0.0"
},
"optionalDependencies": {
"fsevents": "^2.3.2"
},
"devDependencies": {
"@types/node": "^18.0.0"
}
}// platform-specific-module.ts
export async function loadPlatformModule() {
if (process.platform === "win32") {
return await import("./windows/module");
} else if (process.platform === "darwin") {
return await import("./macos/module");
} else {
return await import("./linux/module");
}
}
// Graceful fallback for optional dependencies
export function useFSEvents() {
try {
// fsevents is macOS only
if (process.platform === "darwin") {
const fsevents = require("fsevents");
return fsevents;
}
} catch (error) {
console.warn("fsevents not available, using fallback");
}
// Fallback to chokidar or fs.watch
return require("chokidar");
}Process Management
Process Management
// process-utils.ts
import { spawn, ChildProcess } from "child_process";
export class ProcessUtils {
// Kill process by PID with platform-specific signal
static kill(pid: number, signal?: string): 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");
}
}
// Spawn process with platform-specific handling
static spawnCommand(command: string, args: string[] = []): ChildProcess {
if (process.platform === "win32") {
// Windows requires cmd.exe to run commands
return spawn("cmd", ["/c", command, ...args], {
stdio: "inherit",
shell: true,
});
}
return spawn(command, args, {
stdio: "inherit",
shell: true,
});
}
// Find process by name
static async findProcess(name: string): Promise<number[]> {
if (process.platform === "win32") {
const { stdout } = await execAsync(`tasklist /FI "IMAGENAME eq ${name}"`);
// Parse Windows tasklist output
const pids: number[] = [];
const lines = stdout.split("\n");
for (const line of lines) {
const match = line.match(/\s+(\d+)\s+/);
if (match) pids.push(parseInt(match[1]));
}
return pids;
} else {
const { stdout } = await execAsync(`pgrep ${name}`);
return stdout.split("\n").filter(Boolean).map(Number);
}
}
}Shell Commands
Shell Commands
// shell-utils.ts
import { exec } from "child_process";
import { promisify } from "util";
const execAsync = promisify(exec);
export class ShellUtils {
// Execute command with platform-specific handling
static async execute(command: string): Promise<string> {
try {
const { stdout, stderr } = await execAsync(command, {
shell: this.getShell(),
});
if (stderr) console.error(stderr);
return stdout.trim();
} catch (error) {
throw new Error(`Command failed: ${error.message}`);
}
}
// Get platform-specific shell
static getShell(): string {
if (process.platform === "win32") {
return "cmd.exe";
}
return process.env.SHELL || "/bin/sh";
}
// Platform-specific commands
static async listFiles(directory: string): Promise<string> {
if (process.platform === "win32") {
return this.execute(`dir "${directory}"`);
}
return this.execute(`ls -la "${directory}"`);
}
static async clearScreen(): Promise<void> {
if (process.platform === "win32") {
await this.execute("cls");
} else {
await this.execute("clear");
}
}
static async openFile(filepath: string): Promise<void> {
if (process.platform === "win32") {
await this.execute(`start "" "${filepath}"`);
} else if (process.platform === "darwin") {
await this.execute(`open "${filepath}"`);
} else {
await this.execute(`xdg-open "${filepath}"`);
}
}
}Testing Across Platforms
Testing Across Platforms
GitHub Actions Matrix
# .github/workflows/test.yml
name: Cross-Platform Tests
on: [push, pull_request]
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [16, 18, 20]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Platform-specific tests
if: runner.os == 'Windows'
run: npm run test:windows
- name: Platform-specific tests
if: runner.os == 'macOS'
run: npm run test:macos
- name: Platform-specific tests
if: runner.os == 'Linux'
run: npm run test:linuxPlatform-Specific Tests
// tests/platform.test.ts
import { Platform } from "../src/platform-utils";
describe("Platform-specific tests", () => {
describe("File paths", () => {
it("should handle paths correctly", () => {
const configPath = path.join(os.homedir(), "config.json");
if (Platform.isWindows) {
expect(configPath).toMatch(/^[A-Z]:\\/);
} else {
expect(configPath).toMatch(/^\//);
}
});
});
describe.skipIf(Platform.isWindows)("Unix-only tests", () => {
it("should work with symlinks", () => {
// Symlink tests
});
it("should handle file permissions", () => {
// Permission tests
});
});
describe.skipIf(!Platform.isWindows)("Windows-only tests", () => {
it("should work with UNC paths", () => {
// UNC path tests
});
it("should handle drive letters", () => {
// Drive letter tests
});
});
});#!/bin/bash
# scaffold-tests.sh - Generate test file scaffolding
# Usage: ./scaffold-tests.sh <source_file> [--framework jest|pytest|mocha]
set -euo pipefail
SOURCE_FILE="${{1:?Usage: $0 <source_file> [--framework jest|pytest|mocha]}}"
FRAMEWORK="${{2:-jest}}"
echo "Scaffolding tests for: $SOURCE_FILE (framework: $FRAMEWORK)"
# TODO: Implement test scaffolding logic
# - Parse source file for exported functions/classes
# - Generate test stubs for each export
# - Include setup/teardown boilerplate
# - Add common assertion patterns
echo "Test scaffolding complete."
// Test Template
// TODO: Customize for your testing framework and project
describe('ModuleName', () => {
// Setup
beforeEach(() => {
// TODO: Add test setup
});
afterEach(() => {
// TODO: Add cleanup
});
describe('functionName', () => {
it('should handle the happy path', () => {
// TODO: Add assertion
});
it('should handle edge cases', () => {
// TODO: Add edge case tests
});
it('should handle errors gracefully', () => {
// TODO: Add error handling tests
});
});
});
Related skills
FAQ
Which platforms does cross-platform-compatibility cover?
cross-platform-compatibility addresses Windows, macOS, and Linux desktop portability—with guidance on paths, shells, permissions, encodings, and dependencies—and supports auditing iOS and Android build-target concerns before release.
What is the core path guidance?
cross-platform-compatibility replaces hardcoded C:\ or /home/user paths with Node path.join, path.resolve, and os.homedir() so configuration and data files resolve correctly on every supported operating system.
Where are detailed implementations?
cross-platform-compatibility links 11 reference guides under references/, covering file-path-handling, platform-detection, shell-commands, line-endings, testing-across-platforms, and build-configuration topics.