Handling Zip Path Separators for Linux/cPanel Compatibility
The Problem
When creating a zip file on a Windows machine for use on a Linux-based server (like a typical cPanel/FTP environment), a common issue arises with directory path separators.
- Windows uses backslashes (
\):assets\js\script.js - Linux/Zip Standard uses forward slashes (
/):assets/js/script.js
PowerShell's default Compress-Archive cmdlet, and even some underlying .NET methods, may create zip archives using Windows-style backslashes. When a Linux server tries to unzip this archive, it doesn't recognize the backslash as a directory separator. Instead of creating a js folder inside an assets folder, it might create a single file literally named assets\js\script.js, which breaks the application (e.g., a WordPress theme).
The Solution: Manual Zip Creation
To guarantee that the zip file uses the correct forward-slash separators, we must bypass the high-level cmdlets and manually construct the archive. This gives us explicit control over the name of each entry within the zip file.
The following PowerShell code, implemented in scripts/bundle.ps1, demonstrates the correct approach.
# Manually create ZipArchive to enforce forward slashes for Linux/FTP compatibility
Add-Type -AssemblyName System.IO.Compression.FileSystem
$ZipArchive = [System.IO.Compression.ZipFile]::Open($ZipPath, [System.IO.Compression.ZipArchiveMode]::Create)
$ThemeBuildDirFull = (Get-Item $ThemeBuildDir).FullName
$Files = Get-ChildItem -Path $ThemeBuildDir -Recurse -File
foreach ($File in $Files) {
# Get the path of the file relative to the source directory
$RelativePath = $File.FullName.Substring($ThemeBuildDirFull.Length + 1)
# CRITICAL: Replace Windows backslashes with Linux/Zip-standard forward slashes
$EntryName = $RelativePath -replace '\\', '/'
# Add the file to the archive with the corrected entry name
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($ZipArchive, $File.FullName, $EntryName)
}
# Finalize and close the zip file
$ZipArchive.Dispose()
How It Works
Add-Type: Loads the necessary .NET assembly for advanced file compression operations.[System.IO.Compression.ZipFile]::Open(...): Creates a new, empty zip archive stream that we can write to.Get-ChildItem -Recurse -File: Gathers a list of all files (not directories) within our source build directory.foreach ($File in $Files): We loop through each file we need to add.$File.FullName.Substring(...): This calculates the file's path relative to the build directory (e.g.,assets\js\script.js).$RelativePath -replace '\\', '/': This is the most important step. It takes the relative path and explicitly replaces every backslash (\) with a forward slash (/).CreateEntryFromFile(...): This adds the current file to the zip archive, but it uses our corrected$EntryName(with forward slashes) as the internal path.$ZipArchive.Dispose(): This closes the stream and saves the final, correctly formatted zip file to disk.
By following this method, the bundle.ps1 script produces a universally compatible zip file that will be correctly extracted on any server environment.