Automated summary
This skill guides writing portable code across Windows, macOS and Linux, covering file path construction, platform detection, OS-specific dependencies and testing strategies.
Strengths
- Provides contrastive bad-versus-good examples for path handling in Node.js, Python and Go
- Details OS, architecture and home/temp directory detection with ready-to-use snippets
- Spans multiple ecosystems (TypeScript, Python, Go) instead of a single language
- Addresses concrete pitfalls such as hardcoded separators and path normalization
Limitations
- The supplied content stops mid-way through the platform detection section, leaving dependency and testing parts incomplete
- Examples focus on Node.js, Python and Go; other languages are not addressed
- Offers little guidance on line endings, encodings and multi-OS continuous integration despite mentioning them
Use it when writing or reviewing code intended to run on several operating systems or dealing with paths, environment variables and OS-specific dependencies.
Avoid it for strictly single-platform code where these abstractions would only add needless complexity.
Security analysis
SafeThe skill is a purely informational guide with code examples for cross-platform development. It contains no executable commands, no network requests, no file deletion, no obfuscation, and no data exfiltration. The code snippets are illustrative and do not instruct the AI agent to perform any actions on the user's system.
No concerns found
How this analysis is madeExamples
My Node.js app hardcodes C:\Users\me\config.json and breaks on macOS. Rewrite the path handling so it works on Windows, macOS and Linux.Add platform detection to my CLI so it picks the right temp directory and shell command on Windows, macOS and Linux.Refactor this Python script that builds paths with string concatenation to use pathlib so it runs cross-platform.name: cross-platform-compatibility description: Handle cross-platform compatibility including file paths, environment detection, platform-specific dependencies, and testing across Windows, macOS, and Linux. Use when dealing with platform-specific code or OS compatibility.
Cross-Platform Compatibility
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
Instructions
1. 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")
}
2. 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')
3. 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);
4. 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
);
}
}
5. 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}"`);
}
}
}
6. 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 });
}
}
7. 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);
}
}
}
8. 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");
}
9. 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:linux
Platform-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
});
});
});
10. 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";
}
}
11. 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,
}),
],
};
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
Common Patterns
Pattern 1: Platform Factory
export interface PlatformHandler {
openFile(path: string): Promise<void>;
getConfigPath(): string;
}
class WindowsHandler implements PlatformHandler {
async openFile(path: string) {
await exec(`start "" "${path}"`);
}
getConfigPath() {
return path.join(process.env.APPDATA!, "myapp", "config.json");
}
}
class UnixHandler implements PlatformHandler {
async openFile(path: string) {
await exec(`xdg-open "${path}"`);
}
getConfigPath() {
return path.join(os.homedir(), ".config", "myapp", "config.json");
}
}
export function createPlatformHandler(): PlatformHandler {
return process.platform === "win32"
? new WindowsHandler()
: new UnixHandler();
}
Pattern 2: Conditional Imports
const platformModule = await (async () => {
switch (process.platform) {
case "win32":
return import("./platforms/windows");
case "darwin":
return import("./platforms/macos");
default:
return import("./platforms/linux");
}
})();
Tools & Resources
- cross-env: Set environment variables cross-platform
- cross-spawn: Cross-platform spawn
- rimraf: Cross-platform rm -rf
- mkdirp: Cross-platform mkdir -p
- cpy: Cross-platform file copying
- del: Cross-platform file deletion
- execa: Better child_process
- pkg: Package Node.js apps for all platforms
From the file to a first result
The download contains exactly the content displayed on this page. This copy may differ from the latest version in the source repository.
View the source and supporting filesThis download contains SKILL.md only. Any scripts, references, templates and dependencies are not included. Get the required files from the source, preserving their directory structure, before continuing.
1. Download the file
Download SKILL.mdFrom the root of a test project, save the file as .skill-downloads/cross-platform-compatibility-mmbizkq6/SKILL.md, or use the command below. This folder is for inspecting the skill before installation.
Command for macOS/Linux or WSL: requires a POSIX shell and curl. On Windows without WSL, use the download button and create the folder manually.
mkdir -p '.skill-downloads/cross-platform-compatibility-mmbizkq6' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/cross-platform-compatibility-mmbizkq6/download' --output '.skill-downloads/cross-platform-compatibility-mmbizkq6/SKILL.md.part' && mv '.skill-downloads/cross-platform-compatibility-mmbizkq6/SKILL.md.part' '.skill-downloads/cross-platform-compatibility-mmbizkq6/SKILL.md'2. Check prerequisites
Read SKILL.md before using it. If it references scripts or external services, inspect them and prepare the required access in your test environment.
No recognized prerequisites section in this file. This does not mean it is self-contained: libraries, tools and access requirements may be mentioned elsewhere in the content or source repository.
The platforms on this page are catalog indications. The paths below follow tool documentation; they do not prove this skill has been run in those tools. No minimum tool version is confirmed for this listing.
3. Load the skill
After inspection, copy the complete folder into your project using one of the commands below, then open a new tool session. The command stops if a folder with the same name already exists: compare the files before replacing it.
Claude Code
test -f '.skill-downloads/cross-platform-compatibility-mmbizkq6/SKILL.md' && test -s '.skill-downloads/cross-platform-compatibility-mmbizkq6/SKILL.md' && mkdir -p '.claude/skills' && test ! -e '.claude/skills/cross-platform-compatibility' && cp -R '.skill-downloads/cross-platform-compatibility-mmbizkq6' '.claude/skills/cross-platform-compatibility'Cursor
test -f '.skill-downloads/cross-platform-compatibility-mmbizkq6/SKILL.md' && test -s '.skill-downloads/cross-platform-compatibility-mmbizkq6/SKILL.md' && mkdir -p '.cursor/skills' && test ! -e '.cursor/skills/cross-platform-compatibility' && cp -R '.skill-downloads/cross-platform-compatibility-mmbizkq6' '.cursor/skills/cross-platform-compatibility'Codex
test -f '.skill-downloads/cross-platform-compatibility-mmbizkq6/SKILL.md' && test -s '.skill-downloads/cross-platform-compatibility-mmbizkq6/SKILL.md' && mkdir -p '.agents/skills' && test ! -e '.agents/skills/cross-platform-compatibility' && cp -R '.skill-downloads/cross-platform-compatibility-mmbizkq6' '.agents/skills/cross-platform-compatibility'OpenSkills
OpenSkills must already be installed. The local install/read procedure is checked with OpenSkills 1.5.0, which requires Node.js ≥ 20.6.0. Catalog slugs are not resolved automatically.
Official OpenSkills installationopenskills install './.skill-downloads/cross-platform-compatibility-mmbizkq6'openskills read 'cross-platform-compatibility-mmbizkq6'Reading should display the installed file content and its base directory. It does not run the skill or install its dependencies.
For an agent that reads AGENTS.md, then sync the index. This command changes AGENTS.md: review the diff before using it.
openskills syncManual method or another tool: attach SKILL.md to a conversation, or open it in an agent that can read files. Include any required supporting resources. No universal folder is recognized by every tool.
4. Try a first task
Use a short, synthetic input: an API endpoint, a small document or an interface brief. In a new conversation, attach the file or give the agent its path, then adapt this request:
Read .skill-downloads/cross-platform-compatibility-mmbizkq6/SKILL.md and apply its instructions to this example: [describe a test input]. Identify any missing files, dependencies or access. Produce the result requested by the skill without inventing data or running unreviewed scripts.Expected result: output matching the skill instructions, or a precise list of missing prerequisites. A read confirmation alone does not validate a first use. If an external resource is unavailable, that path remains untested.
React Hook Form Best Practices
Development
Master React Hook Form: avoid anti-patterns, manage subscriptions, and handle reset, dirty state, and controlled inputs correctly.
Emotion to Mantine Migration
Development
Migrate Emotion styled-components to Mantine components with style props and CSS modules. Use when converting .styled.tsx files or removing @emotion imports.
Scope Creep Detector
Development
Analyzes git diffs against a stated intent to detect scope creep, unrelated files, broad PRs, and oversized changes, then suggests keep, split, or justify actions.