Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 221 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,45 @@ jobs:
else
VERSION="snapshot-${GITHUB_SHA::7}"
fi
TRIMMED_VERSION="${VERSION#v}"
MAX_MSIX_COMPONENT=65535
build_snapshot_msix_version() {
local max_component="$1"
local run_number="${GITHUB_RUN_NUMBER:-0}"
local year month_day seq
year="$(date -u +%Y)"
month_day="$(date -u +%m%d)"
# MSIX version components are limited to the range 0..=65535.
# If GITHUB_RUN_NUMBER is unavailable we fall back to 0 for reproducible snapshot metadata.
# `10#` forces base-10 parsing so zero-padded run numbers are not treated as octal.
# This yields 65,536 distinct values (0..=max_component) before wrapping on a later run,
# which is well beyond the expected number of snapshot packages emitted within one UTC bucket.
seq="$((10#${run_number} % (max_component + 1)))"
printf '%s\n' "${year}.${month_day}.${seq}.0"
}
if [[ "$TRIMMED_VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
PACKAGE_VERSION="$TRIMMED_VERSION"
MSIX_MAJOR="${BASH_REMATCH[1]}"
MSIX_MINOR="${BASH_REMATCH[2]}"
MSIX_PATCH="${BASH_REMATCH[3]}"
# Direct semver→MSIX mapping is only valid while every component fits the
# Windows MSIX 16-bit component limit (0..=MAX_MSIX_COMPONENT).
if (( MSIX_MAJOR <= MAX_MSIX_COMPONENT && MSIX_MINOR <= MAX_MSIX_COMPONENT && MSIX_PATCH <= MAX_MSIX_COMPONENT )); then
MSIX_VERSION="${MSIX_MAJOR}.${MSIX_MINOR}.${MSIX_PATCH}.0"
else
echo "::warning::Release version '$VERSION' has at least one MSIX component above the limit (0..=${MAX_MSIX_COMPONENT}); using snapshot-style MSIX metadata."
MSIX_VERSION="$(build_snapshot_msix_version "$MAX_MSIX_COMPONENT")"
fi
else
echo "::warning::Release version '$VERSION' is not a plain semver tag; generating snapshot-style native package metadata."
DATE_STAMP="$(date -u +%Y%m%d)"
RUN_NUMBER="${GITHUB_RUN_NUMBER:-0}"
PACKAGE_VERSION="0.0.0+${DATE_STAMP}.${RUN_NUMBER}"
MSIX_VERSION="$(build_snapshot_msix_version "$MAX_MSIX_COMPONENT")"
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "package_version=$PACKAGE_VERSION" >> "$GITHUB_OUTPUT"
echo "msix_version=$MSIX_VERSION" >> "$GITHUB_OUTPUT"

- name: Build release binary
run: cargo build --release
Expand All @@ -68,18 +106,73 @@ jobs:
chmod +x "$BUNDLE_DIR/sort_it_now"
cp README.md "$BUNDLE_DIR/"
cp scripts/install-unix.sh "$BUNDLE_DIR/install.sh"
cp scripts/uninstall-unix.sh "$BUNDLE_DIR/uninstall.sh"
chmod +x "$BUNDLE_DIR/install.sh"
chmod +x "$BUNDLE_DIR/uninstall.sh"
tar -czf "$ARCHIVE_PATH" "$BUNDLE_DIR"
if [[ "$RUNNER_OS" == "Linux" ]]; then
sha256sum "$ARCHIVE_PATH" > "$ARCHIVE_PATH.sha256"
else
shasum -a 256 "$ARCHIVE_PATH" | awk '{printf "%s %s\n", $1, $2}' > "$ARCHIVE_PATH.sha256"
fi

- name: Package native installer (.deb)
if: matrix.os == 'ubuntu-latest'
env:
VERSION: ${{ steps.meta.outputs.version }}
PACKAGE_VERSION: ${{ steps.meta.outputs.package_version }}
SUFFIX: ${{ matrix.artifact_suffix }}
shell: bash
run: |
set -eux
PKG_ROOT="pkgroot"
mkdir -p "$PKG_ROOT/DEBIAN" "$PKG_ROOT/usr/bin" "$PKG_ROOT/usr/share/doc/sort-it-now"
cp target/release/sort_it_now "$PKG_ROOT/usr/bin/"
chmod 755 "$PKG_ROOT/usr/bin/sort_it_now"
cp README.md "$PKG_ROOT/usr/share/doc/sort-it-now/README.md"
cat > "$PKG_ROOT/DEBIAN/control" <<EOF
Package: sort-it-now
Version: ${PACKAGE_VERSION}
Section: utils
Priority: optional
Architecture: amd64
Maintainer: JosunLP <noreply@github.com>
Description: 3D box packing optimizer with web UI
Sort-it-now optimizes cuboid placement with stability, weight,
balance and live visualization support.
EOF
DEB_PATH="sort-it-now-${VERSION}-${SUFFIX}.deb"
dpkg-deb --root-owner-group --build "$PKG_ROOT" "$DEB_PATH"
sha256sum "$DEB_PATH" > "$DEB_PATH.sha256"

- name: Package native installer (.pkg)
if: startsWith(matrix.os, 'macos')
env:
VERSION: ${{ steps.meta.outputs.version }}
PACKAGE_VERSION: ${{ steps.meta.outputs.package_version }}
SUFFIX: ${{ matrix.artifact_suffix }}
shell: bash
run: |
set -eux
PKG_ROOT="pkgroot"
mkdir -p "$PKG_ROOT/usr/local/bin" "$PKG_ROOT/usr/local/share/doc/sort-it-now"
cp target/release/sort_it_now "$PKG_ROOT/usr/local/bin/"
chmod 755 "$PKG_ROOT/usr/local/bin/sort_it_now"
cp README.md "$PKG_ROOT/usr/local/share/doc/sort-it-now/README.md"
PKG_PATH="sort-it-now-${VERSION}-${SUFFIX}.pkg"
pkgbuild \
--root "$PKG_ROOT" \
--identifier "io.github.josunlp.sort-it-now" \
--version "${PACKAGE_VERSION}" \
--install-location "/" \
"$PKG_PATH"
shasum -a 256 "$PKG_PATH" | awk '{printf "%s %s\n", $1, $2}' > "$PKG_PATH.sha256"

- name: Package artifact (Windows)
if: matrix.os == 'windows-latest'
env:
VERSION: ${{ steps.meta.outputs.version }}
MSIX_VERSION: ${{ steps.meta.outputs.msix_version }}
SUFFIX: ${{ matrix.artifact_suffix }}
EXT: ${{ matrix.archive_extension }}
shell: pwsh
Expand All @@ -90,17 +183,144 @@ jobs:
Copy-Item -Path "target\release\sort_it_now.exe" -Destination (Join-Path $bundleDir "sort_it_now.exe")
Copy-Item -Path "README.md" -Destination (Join-Path $bundleDir "README.md")
Copy-Item -Path "scripts\install-windows.ps1" -Destination (Join-Path $bundleDir "install.ps1")
Copy-Item -Path "scripts\uninstall-windows.ps1" -Destination (Join-Path $bundleDir "uninstall.ps1")
Compress-Archive -Path $bundleDir -DestinationPath $archivePath -Force
Get-FileHash -Path $archivePath -Algorithm SHA256 | Select-Object -ExpandProperty Hash | Out-File -FilePath "$archivePath.sha256" -Encoding ASCII

- name: Upload artifact
$msixDir = Join-Path $PWD "msix"
$assetsDir = Join-Path $msixDir "Assets"
New-Item -ItemType Directory -Force -Path $msixDir, $assetsDir | Out-Null
Copy-Item -Path "target\release\sort_it_now.exe" -Destination (Join-Path $msixDir "sort_it_now.exe") -Force

Add-Type -AssemblyName System.Drawing
function New-SolidPng {
param(
[string]$Path,
[int]$Size
)

$bitmap = New-Object System.Drawing.Bitmap $Size, $Size
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
$graphics.Clear([System.Drawing.Color]::FromArgb(255, 35, 117, 192))
$brush = New-Object System.Drawing.SolidBrush ([System.Drawing.Color]::White)
$font = New-Object System.Drawing.Font("Arial", [Math]::Max([int]($Size / 4), 10), [System.Drawing.FontStyle]::Bold)
$graphics.DrawString("SIN", $font, $brush, 4, [Math]::Max([int]($Size / 3), 4))
$bitmap.Save($Path, [System.Drawing.Imaging.ImageFormat]::Png)
$graphics.Dispose()
$brush.Dispose()
$font.Dispose()
$bitmap.Dispose()
}

New-SolidPng -Path (Join-Path $assetsDir "Square44x44Logo.png") -Size 44
New-SolidPng -Path (Join-Path $assetsDir "Square150x150Logo.png") -Size 150
New-SolidPng -Path (Join-Path $assetsDir "StoreLogo.png") -Size 50

@"
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap desktop6 rescap">
<Identity Name="JosunLP.SortItNow" Publisher="CN=SortItNow" Version="$env:MSIX_VERSION" ProcessorArchitecture="x64" />
<Properties>
<DisplayName>Sort It Now</DisplayName>
<PublisherDisplayName>JosunLP</PublisherDisplayName>
<Description>3D box packing optimizer</Description>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Resources>
<Resource Language="en-us" />
</Resources>
<Dependencies>
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.26100.0" />
</Dependencies>
<Applications>
<Application Id="SortItNow" Executable="sort_it_now.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements
DisplayName="Sort It Now"
Description="3D box packing optimizer"
BackgroundColor="#2375C0"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png" />
<Extensions>
<desktop6:Extension Category="windows.fullTrustProcess" Executable="sort_it_now.exe" />
</Extensions>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
</Capabilities>
</Package>
"@ | Out-File -FilePath (Join-Path $msixDir "AppxManifest.xml") -Encoding utf8

$kitBin = Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\bin\*\x64" -Directory |
Sort-Object FullName -Descending |
Select-Object -First 1
if (-not $kitBin) {
throw "Could not locate Windows SDK tools for MSIX packaging."
}

$makeAppx = Join-Path $kitBin.FullName "makeappx.exe"
$signtool = Join-Path $kitBin.FullName "signtool.exe"
$msixPath = "sort-it-now-$env:VERSION-$env:SUFFIX.msix"
& $makeAppx pack /d $msixDir /p $msixPath /o

$cert = New-SelfSignedCertificate `
-Type Custom `
-Subject "CN=SortItNow" `
-KeyUsage DigitalSignature `
-FriendlyName "Sort It Now CI Signing" `
-TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.3") `
-CertStoreLocation "Cert:\CurrentUser\My"
$generatedPassword = [guid]::NewGuid().ToString("N")
$passwordSecure = ConvertTo-SecureString -String $generatedPassword -AsPlainText -Force
$pfxPath = Join-Path $PWD "sort-it-now-ci-signing.pfx"
$cerPath = "sort-it-now-$env:VERSION-$env:SUFFIX.cer"
Export-PfxCertificate -Cert $cert -FilePath $pfxPath -Password $passwordSecure | Out-Null
Export-Certificate -Cert $cert -FilePath $cerPath | Out-Null
& $signtool sign /fd SHA256 /f $pfxPath /p $generatedPassword $msixPath
Remove-Item -Path $pfxPath -Force
Get-FileHash -Path $msixPath -Algorithm SHA256 | Select-Object -ExpandProperty Hash | Out-File -FilePath "$msixPath.sha256" -Encoding ASCII

- name: Upload archive artifact
uses: actions/upload-artifact@v4
with:
name: sort-it-now-${{ steps.meta.outputs.version }}-${{ matrix.artifact_suffix }}
path: |
sort-it-now-${{ steps.meta.outputs.version }}-${{ matrix.artifact_suffix }}.${{ matrix.archive_extension }}
sort-it-now-${{ steps.meta.outputs.version }}-${{ matrix.artifact_suffix }}.${{ matrix.archive_extension }}.sha256

- name: Upload Linux installer artifact
if: matrix.os == 'ubuntu-latest'
uses: actions/upload-artifact@v4
with:
name: sort-it-now-${{ steps.meta.outputs.version }}-${{ matrix.artifact_suffix }}-native
path: |
sort-it-now-${{ steps.meta.outputs.version }}-${{ matrix.artifact_suffix }}.deb
sort-it-now-${{ steps.meta.outputs.version }}-${{ matrix.artifact_suffix }}.deb.sha256

- name: Upload macOS installer artifact
if: startsWith(matrix.os, 'macos')
uses: actions/upload-artifact@v4
with:
name: sort-it-now-${{ steps.meta.outputs.version }}-${{ matrix.artifact_suffix }}-native
path: |
sort-it-now-${{ steps.meta.outputs.version }}-${{ matrix.artifact_suffix }}.pkg
sort-it-now-${{ steps.meta.outputs.version }}-${{ matrix.artifact_suffix }}.pkg.sha256

- name: Upload Windows installer artifact
if: matrix.os == 'windows-latest'
uses: actions/upload-artifact@v4
with:
name: sort-it-now-${{ steps.meta.outputs.version }}-${{ matrix.artifact_suffix }}-native
path: |
sort-it-now-${{ steps.meta.outputs.version }}-${{ matrix.artifact_suffix }}.msix
sort-it-now-${{ steps.meta.outputs.version }}-${{ matrix.artifact_suffix }}.msix.sha256
sort-it-now-${{ steps.meta.outputs.version }}-${{ matrix.artifact_suffix }}.cer

publish:
name: Publish GitHub Release
runs-on: ubuntu-latest
Expand Down
Loading
Loading