From 8c4ffc52b7138526f09d2f9d2060714c7de8750c Mon Sep 17 00:00:00 2001 From: Omar Date: Mon, 18 Aug 2025 19:03:50 -0400 Subject: [PATCH 1/2] refactor: update pack.ps1 script for improved package building and publishing --- pack.ps1 | 236 +++++++++++++++++++++++-------------------------------- 1 file changed, 98 insertions(+), 138 deletions(-) diff --git a/pack.ps1 b/pack.ps1 index d8b4625..0054966 100644 --- a/pack.ps1 +++ b/pack.ps1 @@ -1,22 +1,27 @@ -<# +<# .SYNOPSIS -Packs the Unity package into Packages/, bumps version, and pushes a release branch (and optional tag). - -.DESCRIPTION -- Copies selected Assets/* folders into Packages/. -- Generates .meta files for folders/text so Unity won't complain in immutable Packages/. -- Writes a package-local .gitattributes to avoid Git LFS/text filters breaking binaries. -- Creates/updates a release branch (default: release-v) and optionally an annotated tag v. -- Stages only Packages/. +Build a UPM package from a Unity project and publish it to an orphan "package-only" branch +so that the repository root IS the package (no ?path= needed). + +.EXAMPLE +./pack.ps1 -Version 1.1.0 -TagRelease +# -> pushes branch upm/v1.1.0 and tag v1.1.0 +# Install URL: https://github.com//.git#upm/v1.1.0 (or #v1.1.0) + +.NOTES +- The script copies from Assets/* into a staging package tree, preserving GUIDs when possible. +- Uses a temporary git worktree to create an orphan branch that contains ONLY the package. +- The working tree of your main repo must be clean. #> param( - [Parameter(Mandatory=$true)][string]$Version, + [Parameter(Mandatory = $true)][string]$Version, + # Git options [string]$BaseBranch = "main", - [string]$BranchName = "", # default: release-v - [switch]$ForceBranch, # push with --force-with-lease - [switch]$TagRelease, # also create/push tag v + [string]$BranchName = "", # default: upm/v + [switch]$ForceBranch, # allow overwriting existing branch + [switch]$TagRelease, # also create/push tag v # package.json defaults (used when creating a new one or when -OverrideCoreMetadata is set) [string]$PackageName = "com.gyoudnova.handrecognition", @@ -26,19 +31,18 @@ param( [string]$UnityRelease = "39f1", [string]$AuthorName = "Gyoud Nova", - [switch]$OverrideCoreMetadata, # overwrite name/display/unity/description in package.json - [switch]$RebuildSamples # rebuild samples[] from Samples~ subfolders + [switch]$OverrideCoreMetadata, # overwrite name/display/unity/description in package.json + [switch]$RebuildSamples # rebuild samples[] from Samples~ subfolders ) # -------------------- Setup -------------------- - $ErrorActionPreference = "Stop" # Accept "v1.2.3" or "1.2.3" if ($Version -match '^[vV](.+)$') { $Version = $Matches[1] } -# Keep branch distinct from tag to avoid ambiguity -if ([string]::IsNullOrWhiteSpace($BranchName)) { $BranchName = "release-v$Version" } +# Branch name default +if ([string]::IsNullOrWhiteSpace($BranchName)) { $BranchName = "upm/v$Version" } function Require($name) { if (-not (Get-Command $name -ErrorAction SilentlyContinue)) { @@ -52,36 +56,24 @@ $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $ProjectRoot = & git -C $ScriptDir rev-parse --show-toplevel 2>$null if (-not $ProjectRoot) { $ProjectRoot = (Resolve-Path $ScriptDir).Path } -# -------------------- Create/reset release branch -------------------- - +# Require clean working tree Push-Location $ProjectRoot - -# Require a clean working tree (stash or commit first if needed) if (git status --porcelain) { Pop-Location - throw "Working tree is not clean." + throw "Working tree is not clean. Stash/commit your changes first." } +# Fetch & sanity-check remote branch git fetch origin | Out-Null -git checkout $BaseBranch | Out-Null -git pull --ff-only | Out-Null - $remoteExists = -not [string]::IsNullOrWhiteSpace((git ls-remote --heads origin $BranchName)) + if ($remoteExists -and -not $ForceBranch) { Pop-Location - throw "Remote branch '$BranchName' already exists. Re-run with -ForceBranch to update it." + throw "Remote branch '$BranchName' already exists. Re-run with -ForceBranch to replace it." } - -if ((git branch --list $BranchName)) { git branch -D $BranchName | Out-Null } -git checkout -B $BranchName $BaseBranch | Out-Null - Pop-Location # -------------------- Paths -------------------- - -$PackagesRoot = Join-Path $ProjectRoot "Packages" -$PackagePath = Join-Path $PackagesRoot $PackageName - $Assets = Join-Path $ProjectRoot "Assets" $Src_Scripts = Join-Path $Assets "Scripts" $Src_Images = Join-Path $Assets "Images" @@ -91,37 +83,28 @@ $Src_Streaming = Join-Path $Assets "StreamingAssets" $Src_Tests = Join-Path $Assets "Tests" $Src_UIToolkit = Join-Path $Assets "UI Toolkit" -$Dst_Editor = Join-Path $PackagePath "Editor" -$Dst_Images = Join-Path $PackagePath "Images" -$Dst_Runtime = Join-Path $PackagePath "Runtime" +# Staging dir (package root at /) +$StageRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("upm-stage-" + [guid]::NewGuid().ToString("N")) +New-Item -ItemType Directory -Force -Path $StageRoot | Out-Null + +# Package-relative destinations (under StageRoot) +$Dst_Editor = Join-Path $StageRoot "Editor" +$Dst_Images = Join-Path $StageRoot "Images" +$Dst_Runtime = Join-Path $StageRoot "Runtime" $Dst_Runtime_Prefabs = Join-Path $Dst_Runtime "Prefabs" -$Dst_Samples = Join-Path $PackagePath "Samples~" # hidden until user imports -$Dst_Streaming = Join-Path $PackagePath "StreamingAssets" -$Dst_Tests = Join-Path $PackagePath "Tests" -$Dst_UIToolkit = Join-Path $PackagePath "UI Toolkit" +$Dst_Samples = Join-Path $StageRoot "Samples~" # hidden until import +$Dst_Streaming = Join-Path $StageRoot "StreamingAssets" +$Dst_Tests = Join-Path $StageRoot "Tests" +$Dst_UIToolkit = Join-Path $StageRoot "UI Toolkit" # -------------------- Helpers -------------------- - function New-Dir([string]$Path) { if (-not (Test-Path -LiteralPath $Path)) { New-Item -ItemType Directory -Force -Path $Path | Out-Null } } - -function Clear-Dir([string]$Path) { - if (Test-Path -LiteralPath $Path) { - Get-ChildItem -LiteralPath $Path -Force | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue - } else { - New-Dir $Path - } -} - function New-GuidHex { ([guid]::NewGuid().ToString("N")) } -<# -.SYNOPSIS -Create a .meta file for a folder if missing. -#> function Write-FolderMeta([string]$FolderPath) { $metaPath = "$FolderPath.meta" if (Test-Path -LiteralPath $metaPath) { return } @@ -138,10 +121,6 @@ DefaultImporter: "@ | Out-File -FilePath $metaPath -Encoding ascii } -<# -.SYNOPSIS -Create a .meta file for a text asset if missing (README, package.json, etc.). -#> function Write-TextMeta([string]$FilePath) { $metaPath = "$FilePath.meta" if (Test-Path -LiteralPath $metaPath) { return } @@ -157,10 +136,6 @@ TextScriptImporter: "@ | Out-File -FilePath $metaPath -Encoding ascii } -<# -.SYNOPSIS -Copy folder root .meta if available (preserves GUID), else synthesize a new one. -#> function Copy-Or-Make-FolderMeta([string]$SrcFolder, [string]$DstFolder) { $srcParent = Split-Path -Parent $SrcFolder $srcLeaf = Split-Path -Leaf $SrcFolder @@ -174,66 +149,42 @@ function Copy-Or-Make-FolderMeta([string]$SrcFolder, [string]$DstFolder) { } } -<# -.SYNOPSIS -Copy the contents of a folder, excluding specific child names and their .meta. -#> function Copy-DirContents { param( - [Parameter(Mandatory=$true)][string]$From, - [Parameter(Mandatory=$true)][string]$To, + [Parameter(Mandatory = $true)][string]$From, + [Parameter(Mandatory = $true)][string]$To, [string[]]$ExcludeChildNames = @() ) - if (-not (Test-Path -LiteralPath $From -PathType Container)) { return } New-Dir $To Get-ChildItem -LiteralPath $From -Force | ForEach-Object { if ($ExcludeChildNames -contains $_.Name) { return } - if ($_.Extension -ieq ".meta") { $base = [IO.Path]::GetFileNameWithoutExtension($_.Name) if ($ExcludeChildNames -contains $base) { return } } - $dest = Join-Path $To $_.Name Copy-Item -LiteralPath $_.FullName -Destination $dest -Recurse -Force -ErrorAction Stop } } -<# -.SYNOPSIS -Copy an entire directory tree's contents into a destination folder. -#> function Copy-TreeTo { param( - [Parameter(Mandatory=$true)][string]$From, - [Parameter(Mandatory=$true)][string]$To + [Parameter(Mandatory = $true)][string]$From, + [Parameter(Mandatory = $true)][string]$To ) if (-not (Test-Path -LiteralPath $From -PathType Container)) { return } New-Dir $To - Get-ChildItem -LiteralPath $From -Force | ForEach-Object { $dest = Join-Path $To $_.Name Copy-Item -LiteralPath $_.FullName -Destination $dest -Recurse -Force -ErrorAction Stop } } -# -------------------- Build package tree -------------------- +# -------------------- Build package tree in $StageRoot -------------------- -New-Dir $PackagesRoot -New-Dir $PackagePath - -# Start clean inside the package -Clear-Dir $Dst_Editor -Clear-Dir $Dst_Images -Clear-Dir $Dst_Runtime -Clear-Dir $Dst_Samples -Clear-Dir $Dst_Streaming -Clear-Dir $Dst_Tests -Clear-Dir $Dst_UIToolkit - -# Editor <- Assets/Scripts (excluding Dev Utilities) +# Editor <- Assets/Scripts (excluding "Dev Utilities") Copy-DirContents -From $Src_Scripts -To $Dst_Editor -ExcludeChildNames @("Dev Utilities") Copy-Or-Make-FolderMeta -SrcFolder $Src_Scripts -DstFolder $Dst_Editor @@ -249,7 +200,7 @@ if (Test-Path -LiteralPath $Src_Prefabs) { Copy-Or-Make-FolderMeta -SrcFolder $Src_Prefabs -DstFolder $Dst_Runtime_Prefabs } -# Samples~ <- Assets/Samples (no Samples~.meta on purpose) +# Samples~ <- Assets/Samples (no Samples~.meta on purpose) Copy-TreeTo -From $Src_Samples -To $Dst_Samples # StreamingAssets <- Assets/StreamingAssets @@ -264,24 +215,19 @@ Copy-Or-Make-FolderMeta -SrcFolder $Src_Tests -DstFolder $Dst_Tests Copy-TreeTo -From $Src_UIToolkit -To $Dst_UIToolkit Copy-Or-Make-FolderMeta -SrcFolder $Src_UIToolkit -DstFolder $Dst_UIToolkit -# Remove stray meta for excluded folder if present -$DevUtilMeta = Join-Path $Dst_Editor "Dev Utilities.meta" -Remove-Item -LiteralPath $DevUtilMeta -Force -ErrorAction SilentlyContinue - -# -------------------- Docs & .gitattributes -------------------- +# Copy top-level docs if present +$ReadmePath = Join-Path $ProjectRoot "README.md" +$LicensePath = Join-Path $ProjectRoot "LICENSE" +if (Test-Path $ReadmePath) { Copy-Item $ReadmePath (Join-Path $StageRoot "README.md") -Force } +if (Test-Path $LicensePath) { Copy-Item $LicensePath (Join-Path $StageRoot "LICENSE") -Force } -$ReadmePath = Join-Path $PackagePath "README.md" -$LicensePath = Join-Path $PackagePath "LICENSE" - -if (Test-Path (Join-Path $ProjectRoot "README.md")) { Copy-Item (Join-Path $ProjectRoot "README.md") $ReadmePath -Force } -if (Test-Path (Join-Path $ProjectRoot "LICENSE")) { Copy-Item (Join-Path $ProjectRoot "LICENSE") $LicensePath -Force } - -foreach ($p in @($ReadmePath,$LicensePath)) { - if (Test-Path -LiteralPath $p) { Write-TextMeta $p } +foreach ($p in @("README.md","LICENSE")) { + $full = Join-Path $StageRoot $p + if (Test-Path -LiteralPath $full) { Write-TextMeta $full } } -# Disable LFS/text filters for files inside the package -$PkgGitAttr = Join-Path $PackagePath ".gitattributes" +# .gitattributes at package root +$PkgGitAttr = Join-Path $StageRoot ".gitattributes" @" * -filter -diff -merge -text *.png -filter -diff -merge -text @@ -302,29 +248,28 @@ $PkgGitAttr = Join-Path $PackagePath ".gitattributes" Write-TextMeta $PkgGitAttr # -------------------- package.json -------------------- - -$PkgJsonPath = Join-Path $PackagePath "package.json" - +$PkgJsonPath = Join-Path $StageRoot "package.json" +# Create new or use existing from stage dir if you copy one before if (Test-Path -LiteralPath $PkgJsonPath) { $pkg = Get-Content -LiteralPath $PkgJsonPath -Raw | ConvertFrom-Json } else { $pkg = [pscustomobject]@{ - name = $PackageName - version = $Version - displayName = $DisplayName - description = $Description - unity = $UnityVersion - unityRelease= $UnityRelease - keywords = @("Webcam","Hand Gesture","Sign language") - author = @{ name = $AuthorName } - dependencies= @{ + name = $PackageName + version = $Version + displayName = $DisplayName + description = $Description + unity = $UnityVersion + unityRelease = $UnityRelease + keywords = @("Webcam","Hand Gesture","Sign language") + author = @{ name = $AuthorName } + dependencies = @{ "com.gilzoide.sqlite-net" = "1.2.3" "com.github.homuler.mediapipe" = "0.16.1" "com.unity.ugui" = "2.0.0" "com.unity.editorcoroutines" = "1.0.0" } - samples = @( - @{ displayName="Sample Menu UI"; description="Contains a sample scene and scripts for a menu UI"; path="Samples~/SampleMenu" }, + samples = @( + @{ displayName="Sample Menu UI"; description="Contains a sample scene and scripts for a menu UI"; path="Samples~/SampleMenu" }, @{ displayName="Sample RollABall"; description="Contains a sample RollABall scene and related scripts"; path="Samples~/Rollaball" } ) } @@ -362,19 +307,30 @@ if ($RebuildSamples) { $pkg | ConvertTo-Json -Depth 20 | Out-File -FilePath $PkgJsonPath -Encoding UTF8 Write-TextMeta $PkgJsonPath -# -------------------- Commit & push -------------------- - +# -------------------- Publish to orphan branch at repo root -------------------- Push-Location $ProjectRoot -# Stage only the package path -git add -- "Packages/$PackageName" +# Create a temporary worktree, then switch it to an ORPHAN branch +$WorktreeDir = Join-Path ([System.IO.Path]::GetTempPath()) ("upm-wt-" + [guid]::NewGuid().ToString("N")) +git worktree add -f $WorktreeDir $BaseBranch | Out-Null -if (-not (git diff --cached --quiet)) { - git commit -m "Release $Version" | Out-Null -} else { - Write-Host "No changes to commit under Packages/$PackageName." +Push-Location $WorktreeDir +git switch --orphan $BranchName | Out-Null + +# Remove everything except .git to ensure branch root is empty +Get-ChildItem -Force | Where-Object { $_.Name -ne ".git" } | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue + +# Copy staged package (root -> repo root) +Copy-Item -Recurse -Force (Join-Path $StageRoot "*") . + +# Ensure .meta for root text files +foreach ($p in @(".gitattributes","README.md","LICENSE","package.json")) { + if (Test-Path -LiteralPath $p) { Write-TextMeta $p } } +git add -A +git commit -m "UPM package v$Version (package-only branch)" | Out-Null + if ($ForceBranch) { git push -u origin $BranchName --force-with-lease | Out-Null } else { @@ -382,17 +338,21 @@ if ($ForceBranch) { } if ($TagRelease) { - git tag -f -a "v$Version" -m "Release $Version" | Out-Null + git tag -f -a "v$Version" -m "UPM package v$Version" | Out-Null git push --force origin tag "v$Version" | Out-Null } -Pop-Location +Pop-Location # worktree +# Remove the temporary worktree and staging dir +git worktree remove $WorktreeDir --force | Out-Null +Pop-Location # project +Remove-Item -Recurse -Force $StageRoot -ErrorAction SilentlyContinue -# Print install URLs +# -------------------- Print install URLs -------------------- Write-Host "" Write-Host "Install via branch:" -Write-Host " https://github.com/GYOUDNova/Nova.git?path=/Packages/$PackageName#$BranchName" +Write-Host " https://github.com//.git#$BranchName" if ($TagRelease) { Write-Host "Install via tag:" - Write-Host " https://github.com/GYOUDNova/Nova.git?path=/Packages/$PackageName#v$Version" -} \ No newline at end of file + Write-Host " https://github.com//.git#v$Version" +} From e8bce1b4b5149579395c0bb3f600e682e5761141 Mon Sep 17 00:00:00 2001 From: Omar Date: Mon, 18 Aug 2025 19:09:01 -0400 Subject: [PATCH 2/2] fix: update install URLs in pack.ps1 to use the correct repository path --- pack.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pack.ps1 b/pack.ps1 index 0054966..4ac29e2 100644 --- a/pack.ps1 +++ b/pack.ps1 @@ -351,8 +351,8 @@ Remove-Item -Recurse -Force $StageRoot -ErrorAction SilentlyContinue # -------------------- Print install URLs -------------------- Write-Host "" Write-Host "Install via branch:" -Write-Host " https://github.com//.git#$BranchName" +Write-Host " https://github.com/GYOUDNova/Nova.git#$BranchName" if ($TagRelease) { Write-Host "Install via tag:" - Write-Host " https://github.com//.git#v$Version" + Write-Host " https://github.com/GYOUDNova/Nova.git#v$Version" }