cross-platform-compatibility

安装量: 127
排名: #6790

安装

npx skills add https://github.com/aj-geddes/useful-ai-prompts --skill cross-platform-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")

}

  1. 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')

  1. 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);

  1. 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 ); } }

  1. 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 { 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 { if (process.platform === 'win32') { return this.execute(dir "${directory}"); } return this.execute(ls -la "${directory}"); }

static async clearScreen(): Promise { if (process.platform === 'win32') { await this.execute('cls'); } else { await this.execute('clear'); } }

static async openFile(filepath: string): Promise { 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}"); } } }

  1. 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 }); } }

  1. 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 { 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); } } }

  1. 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'); }

  1. 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
});

}); });

  1. 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';

} }

  1. 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; 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

返回排行榜