| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- <#
- .SYNOPSIS
- Builds a distributable .zip of the Studiou WC Mail Queue plugin.
- .DESCRIPTION
- Packages only the shippable runtime — PHP, views, assets, languages and the
- user-facing readme. Development artefacts are excluded: the docs/ tree (which
- includes a ~6,000-file vendored copy of WooCommerce), CLAUDE.md, this build
- script, dotfiles and any previous zips.
- Two layouts, because the plugin can run either way:
- -Type MuPlugin (default) Extract the zip straight into wp-content/mu-plugins/.
- The loader stub is placed at the archive root as
- studiou-wc-mail-queue.php, beside the
- studiou-wc-mail-queue/ directory — exactly the
- two-part install the plugin requires. No manual
- copying, so the "forgot the loader stub → silently
- inert" trap cannot happen.
- -Type Plugin A conventional single-folder plugin zip, uploadable
- via Plugins -> Add New -> Upload and installed to
- wp-content/plugins/. The loader stub is not needed
- there; the main file's own plugin header runs it.
- .PARAMETER OutputDir
- Where the .zip is written. Default: D:\@StudioU
- .PARAMETER Type
- MuPlugin (default) or Plugin. See above.
- .PARAMETER PluginDir
- Source plugin directory. Default: the folder this script lives in.
- .EXAMPLE
- .\build-package.ps1
- Builds the mu-plugin package into D:\@StudioU.
- .EXAMPLE
- .\build-package.ps1 -Type Plugin
- Builds a conventional plugin zip instead.
- #>
- [CmdletBinding()]
- param(
- [string] $OutputDir = 'D:\@StudioU',
- [ValidateSet('MuPlugin', 'Plugin')]
- [string] $Type = 'MuPlugin',
- [string] $PluginDir
- )
- $ErrorActionPreference = 'Stop'
- # --- Resolve the plugin directory ------------------------------------------
- if ([string]::IsNullOrWhiteSpace($PluginDir)) {
- if ($PSScriptRoot) {
- $PluginDir = $PSScriptRoot
- } elseif ($MyInvocation.MyCommand.Path) {
- $PluginDir = Split-Path -Parent $MyInvocation.MyCommand.Path
- } else {
- $PluginDir = (Get-Location).Path
- }
- }
- $PluginDir = (Resolve-Path -LiteralPath $PluginDir).Path
- $Slug = 'studiou-wc-mail-queue'
- $MainFile = Join-Path $PluginDir "$Slug.php"
- $LoaderFile = Join-Path $PluginDir "loader\$Slug.php"
- $ScriptName = $MyInvocation.MyCommand.Name
- function Write-Step ($msg) { Write-Host " $msg" -ForegroundColor Cyan }
- function Write-Ok ($msg) { Write-Host " $msg" -ForegroundColor Green }
- function Write-Note ($msg) { Write-Host " $msg" -ForegroundColor DarkGray }
- Write-Host ""
- Write-Host "Studiou WC Mail Queue - package builder" -ForegroundColor White
- Write-Host "----------------------------------------"
- # --- Sanity checks ----------------------------------------------------------
- if (-not (Test-Path -LiteralPath $MainFile)) {
- throw "Main plugin file not found: $MainFile`n(Is -PluginDir pointing at the plugin folder?)"
- }
- if ($Type -eq 'MuPlugin' -and -not (Test-Path -LiteralPath $LoaderFile)) {
- throw "Loader stub not found: $LoaderFile`n(Required for a MuPlugin package.)"
- }
- # --- Read the version from the plugin header --------------------------------
- $header = Get-Content -LiteralPath $MainFile -Raw
- $version = $null
- if ($header -match '(?m)^\s*\*\s*Version:\s*(.+?)\s*$') {
- $version = $Matches[1].Trim()
- }
- if ([string]::IsNullOrWhiteSpace($version)) {
- throw "Could not read 'Version:' from the plugin header in $MainFile"
- }
- Write-Step "Version: $version"
- Write-Step "Type: $Type"
- Write-Step "Source: $PluginDir"
- # --- Warn if the compiled translation is missing ----------------------------
- $moFiles = @(Get-ChildItem -LiteralPath (Join-Path $PluginDir 'languages') -Filter '*.mo' -ErrorAction SilentlyContinue)
- if ($moFiles.Count -eq 0) {
- Write-Warning "No compiled .mo in languages/. The admin UI renders in English until you compile it (Poedit / msgfmt)."
- }
- # --- Prepare output ---------------------------------------------------------
- if (-not (Test-Path -LiteralPath $OutputDir)) {
- New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
- Write-Note "Created output directory: $OutputDir"
- }
- if ($Type -eq 'Plugin') {
- $zipName = "$Slug-$version.plugin.zip"
- } else {
- $zipName = "$Slug-$version.zip"
- }
- $zipPath = Join-Path $OutputDir $zipName
- # --- Stage the files --------------------------------------------------------
- $staging = Join-Path ([System.IO.Path]::GetTempPath()) ("studiou-wcmq-build-" + [guid]::NewGuid().ToString('N'))
- New-Item -ItemType Directory -Path $staging | Out-Null
- try {
- $pluginStage = Join-Path $staging $Slug
- # Directories excluded from the copy. `loader` is excluded for MuPlugin
- # because its stub is hoisted to the archive root; it is kept inside the
- # folder for a conventional Plugin package.
- $excludeDirs = @('docs', '.git', '.github', '.claude', '.vs', 'node_modules')
- if ($Type -eq 'MuPlugin') { $excludeDirs += 'loader' }
- # Files excluded by name / pattern.
- $excludeFiles = @('*.zip', '*.ps1', '.gitignore', '.gitattributes', 'CLAUDE.md',
- 'Thumbs.db', 'desktop.ini', '.DS_Store', $ScriptName)
- Write-Step "Staging files (excluding dev artefacts)..."
- # robocopy prunes excluded directories rather than copying then deleting, so
- # the huge vendored WooCommerce tree under docs/ is never even traversed.
- $roboArgs = @($PluginDir, $pluginStage, '/E',
- '/XD') + $excludeDirs + @('/XF') + $excludeFiles +
- @('/R:1', '/W:1', '/NFL', '/NDL', '/NJH', '/NJS', '/NP', '/NC', '/NS')
- robocopy @roboArgs | Out-Null
- # robocopy exit codes 0-7 are all success; 8+ is a real failure.
- if ($LASTEXITCODE -ge 8) {
- throw "robocopy failed with exit code $LASTEXITCODE while staging files."
- }
- $global:LASTEXITCODE = 0
- if (-not (Test-Path -LiteralPath $MainFile.Replace($PluginDir, $pluginStage))) {
- throw "Staging did not produce the main plugin file - aborting."
- }
- # For a mu-plugin, place the loader stub at the archive root so extraction
- # into wp-content/mu-plugins/ needs no further steps.
- if ($Type -eq 'MuPlugin') {
- Copy-Item -LiteralPath $LoaderFile -Destination (Join-Path $staging "$Slug.php")
- Write-Note "Loader stub placed at archive root."
- }
- # --- Compress -----------------------------------------------------------
- if (Test-Path -LiteralPath $zipPath) {
- Remove-Item -LiteralPath $zipPath -Force
- Write-Note "Replaced existing $zipName"
- }
- Write-Step "Compressing..."
- # NOT Compress-Archive: in Windows PowerShell 5.1 it writes entry paths with
- # backslashes, which violates the ZIP spec (APPNOTE mandates forward slashes).
- # On the Linux host that runs WordPress those extract as flat files with
- # literal backslashes in their names, so the plugin never installs. Build the
- # archive by hand and force forward-slash entry names.
- Add-Type -AssemblyName System.IO.Compression
- Add-Type -AssemblyName System.IO.Compression.FileSystem
- $stagingRoot = $staging.TrimEnd('\')
- $fs = [System.IO.File]::Open($zipPath, [System.IO.FileMode]::Create)
- $archive = New-Object System.IO.Compression.ZipArchive($fs, [System.IO.Compression.ZipArchiveMode]::Create)
- try {
- Get-ChildItem -LiteralPath $staging -Recurse -File | ForEach-Object {
- $rel = $_.FullName.Substring($stagingRoot.Length + 1).Replace('\', '/')
- [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile(
- $archive, $_.FullName, $rel,
- [System.IO.Compression.CompressionLevel]::Optimal) | Out-Null
- }
- }
- finally {
- $archive.Dispose()
- $fs.Dispose()
- }
- }
- finally {
- if (Test-Path -LiteralPath $staging) {
- Remove-Item -LiteralPath $staging -Recurse -Force -ErrorAction SilentlyContinue
- }
- }
- # --- Verify the archive -----------------------------------------------------
- Write-Step "Verifying..."
- Add-Type -AssemblyName System.IO.Compression.FileSystem
- $zip = [System.IO.Compression.ZipFile]::OpenRead($zipPath)
- try {
- $entries = $zip.Entries
- $fileCount = ($entries | Where-Object { $_.Name -ne '' }).Count
- # Guard against dev artefacts leaking in.
- $leaked = $entries | Where-Object {
- $_.FullName -match '(^|/)docs/' -or
- $_.FullName -match '(^|/)\.git/' -or
- $_.FullName -match '(^|/)woocommerce/' -or
- $_.FullName -like '*CLAUDE.md' -or
- $_.FullName -like '*.ps1'
- }
- if ($leaked) {
- $names = ($leaked | Select-Object -First 5 | ForEach-Object { $_.FullName }) -join ', '
- throw "Archive contains files that should have been excluded: $names"
- }
- # Confirm the entry point exists where install expects it.
- if ($Type -eq 'MuPlugin') {
- $needMain = $entries | Where-Object { $_.FullName -eq "$Slug/$Slug.php" }
- $needLoader = $entries | Where-Object { $_.FullName -eq "$Slug.php" }
- if (-not $needMain) { throw "Archive is missing $Slug/$Slug.php" }
- if (-not $needLoader) { throw "Archive is missing the root loader stub $Slug.php" }
- } else {
- $needMain = $entries | Where-Object { $_.FullName -eq "$Slug/$Slug.php" }
- if (-not $needMain) { throw "Archive is missing $Slug/$Slug.php" }
- }
- }
- finally {
- $zip.Dispose()
- }
- # --- Summary ----------------------------------------------------------------
- $sizeKB = [math]::Round((Get-Item -LiteralPath $zipPath).Length / 1KB, 1)
- Write-Host ""
- Write-Ok "Built $zipName"
- Write-Note "Path: $zipPath"
- Write-Note "Size: $sizeKB KB"
- Write-Note "Files: $fileCount"
- Write-Host ""
- if ($Type -eq 'MuPlugin') {
- Write-Host "Install (mu-plugin):" -ForegroundColor White
- Write-Note "Extract the zip directly into wp-content/mu-plugins/ so you get:"
- Write-Note " wp-content/mu-plugins/$Slug.php (loader stub)"
- Write-Note " wp-content/mu-plugins/$Slug/ (plugin)"
- Write-Note "Then: WooCommerce -> Mail Queue -> Enable. Ships disabled."
- } else {
- Write-Host "Install (regular plugin):" -ForegroundColor White
- Write-Note "wp-admin -> Plugins -> Add New -> Upload Plugin -> choose this zip, then Activate."
- Write-Note "Then: WooCommerce -> Mail Queue -> Enable. Ships disabled."
- }
- Write-Host ""
|