This guide documents the complete step-by-step process to generate a full ZPL2PDF release.
The release process is divided into 16 steps that can be executed individually or through the main script. Each step has its own script to allow manual execution in case of failure.
Before starting the release process, make sure you have:
- ✅ .NET SDK 9.0 or higher
- ✅ Git installed and configured
- ✅ PowerShell 7+ (Windows) or Bash (Linux/macOS)
- ✅ Docker Desktop installed and running (for Linux packages and Docker images)
- ✅ Inno Setup 6 installed (for Windows installer)
- ✅ GitHub CLI (
gh) installed and authenticated
- ✅ GitHub account with repository access
- ✅ Docker Hub account (to publish images)
- ✅ GitHub CLI authenticated:
gh auth login - ✅ Docker Hub authenticated:
docker login - ✅ GitHub token with
write:packagesscope (for GHCR)
# Verify tools
dotnet --version
git --version
docker --version
gh --version
# Verify authentication
gh auth status
docker infoAll release artifacts are generated in the release/ folder at the project root:
| Step | Script | Output location |
|---|---|---|
| 7 | Build all platforms | release/ZPL2PDF-v{Version}-win-x64.zip, *-linux-*.tar.gz, *-osx-*.tar.gz, release/SHA256SUMS.txt |
| 8 | Windows installer | installer/Output/ZPL2PDF-Setup-{Version}.exe → copied to release/ZPL2PDF-Setup-{Version}.exe |
| 9 | Linux packages | release/ (.deb, .rpm archive) |
| 13 | Source archives | release/ZPL2PDF-{Version}-source.zip, *-source.tar.gz (created by script 13) |
Step 13 (GitHub release) uploads everything from release/ that matches *{Version}* or SHA256SUMS.txt. The folder release/ is in .gitignore and is not committed.
Scripts live in scripts/release/. Generated artifacts go in release/.
scripts/release/
├── README.md # This file
├── release-main.ps1 # Main script (orchestrates all)
├── _checkpoint-utils.ps1 # Checkpoint utilities
├── show-checkpoint.ps1 # Show checkpoint status
│
├── 01-update-docs.ps1 # Update documentation
├── 02-update-resources.ps1 # Update Resources Messages
├── 03-update-i18n.ps1 # Update i18n files
├── 04-update-resource-keys.ps1 # Update Localization.ResourceKeys
├── 05-update-changelog.ps1 # Update CHANGELOG.md
├── 06-update-dockerfile.ps1 # Update Dockerfile
├── 07-build-all-platforms.ps1 # Build all platforms
├── 08-build-installer.ps1 # Build Windows installer
├── 09-build-linux-packages.ps1 # Build .deb and .rpm packages
├── 10-update-manifests.ps1 # Update WinGet manifests
├── 11-build-docker-images.ps1 # Build Docker images
├── 12-create-version-branch.ps1 # Create version branch and PR
├── 13-create-github-release.ps1 # Create/update GitHub release
├── 14-publish-dockerhub.ps1 # Publish to Docker Hub
├── 15-publish-ghcr.ps1 # Publish to GitHub Package Registry
└── 16-submit-winget-pr.ps1 # Update WinGet fork and create PR
Path note: The release scripts live in
scripts/release/.
If you see older examples withrelease-scripts, use.\scripts\release\...instead.
# Run all steps in sequence
.\scripts\release\release-main.ps1 -Version "3.1.0"
# Run with dry-run (test without publishing)
.\scripts\release\release-main.ps1 -Version "3.1.0" -DryRun
# Skip specific steps
.\scripts\release\release-main.ps1 -Version "3.1.0" -SkipDocker -SkipWinGet
# Resume interrupted release (skips already completed steps)
.\scripts\release\release-main.ps1 -Version "3.1.0" -Resume
# Continue from a specific step
.\scripts\release\release-main.ps1 -Version "3.1.0" -StartFromStep 8# Run only a specific step
.\scripts\release\01-update-docs.ps1 -Version "3.1.0"
.\scripts\release\07-build-all-platforms.ps1 -Version "3.1.0"# View current checkpoint status
.\scripts\release\show-checkpoint.ps1 -Version "3.1.0"The release system uses a checkpoint file to track progress and allow resuming from where it stopped.
When you start a release, a .release-checkpoint-{Version}.json file is created at the project root:
{
"Version": "3.1.0",
"StartedAt": "2025-12-20 10:30:00",
"LastUpdated": "2025-12-20 11:45:00",
"CompletedSteps": [1, 2, 3, 4, 5, 6, 7],
"FailedSteps": [],
"SkippedSteps": [9, 11],
"Data": {}
}- ✅ Automatic tracking: Each step saves its status upon completion
- ✅ Resume release: Use
-Resumeto skip already completed steps - ✅ Failure history: Failed steps are recorded
- ✅ Persistent data: Important information is saved in the checkpoint
# Start release (creates checkpoint automatically)
.\scripts\release\release-main.ps1 -Version "3.1.0"
# If something fails, resume from where it stopped
.\scripts\release\release-main.ps1 -Version "3.1.0" -Resume
# Continue from a specific step
.\scripts\release\release-main.ps1 -Version "3.1.0" -StartFromStep 8
# View checkpoint status (via utility script)
# Status is shown automatically when using -ResumeThe checkpoint file is saved at the project root:
.release-checkpoint-3.1.0.json
To clear the checkpoint and start from scratch:
# Checkpoint is kept after release for history
# To clear manually, delete the file:
Remove-Item .release-checkpoint-3.1.0.jsonScript: 01-update-docs.ps1
What it does:
- Updates version in
docs/README.md - Updates version in other relevant documentation files
- Updates version references in guides
Modified files:
docs/README.md- Other
.mdfiles that reference version
Execution:
.\scripts\release\01-update-docs.ps1 -Version "3.1.0"Script: 02-update-resources.ps1
What it does:
- Updates version in all
Resources/Messages.*.resxfiles - Maintains consistency across all languages
Modified files:
Resources/Messages.en.resxResources/Messages.pt-BR.resxResources/Messages.es.resxResources/Messages.fr.resxResources/Messages.de.resxResources/Messages.it.resxResources/Messages.ja.resxResources/Messages.zh.resx
Execution:
.\scripts\release\02-update-resources.ps1 -Version "3.1.0"Script: 03-update-i18n.ps1
What it does:
- Updates version in internationalization files
- Updates language documentation
Modified files:
- Files in
docs/i18n/
Execution:
.\scripts\release\03-update-i18n.ps1 -Version "3.1.0"Script: 04-update-resource-keys.ps1
What it does:
- Checks and updates
src/Shared/Localization/ResourceKeys.csif necessary - Ensures all keys are synchronized
Modified files:
src/Shared/Localization/ResourceKeys.cs
Execution:
.\scripts\release\04-update-resource-keys.ps1 -Version "3.1.0"Script: 05-update-changelog.ps1
What it does:
- Updates
CHANGELOG.mdwith the new version - Adds release date
- Moves items from
[Unreleased]to the new version
Modified files:
CHANGELOG.md
Execution:
.\scripts\release\05-update-changelog.ps1 -Version "3.1.0"Script: 06-update-dockerfile.ps1
What it does:
- Updates version in
Dockerfile(LABEL version) - Updates version references if any
Modified files:
Dockerfile
Execution:
.\scripts\release\06-update-dockerfile.ps1 -Version "3.1.0"Script: 07-build-all-platforms.ps1
What it does:
- Builds for 8 platforms:
- Windows: x64, x86, ARM64
- Linux: x64, ARM64, ARM
- macOS: x64, ARM64
- Creates compressed files (.zip for Windows, .tar.gz for Linux/macOS)
- Generates SHA256 checksums
Generated files:
release/ZPL2PDF-v{Version}-win-x64.ziprelease/ZPL2PDF-v{Version}-win-x86.ziprelease/ZPL2PDF-v{Version}-win-arm64.ziprelease/ZPL2PDF-v{Version}-linux-x64.tar.gzrelease/ZPL2PDF-v{Version}-linux-arm64.tar.gzrelease/ZPL2PDF-v{Version}-linux-arm.tar.gzrelease/ZPL2PDF-v{Version}-osx-x64.tar.gzrelease/ZPL2PDF-v{Version}-osx-arm64.tar.gzrelease/SHA256SUMS.txt
Execution:
.\scripts\release\07-build-all-platforms.ps1 -Version "3.1.0" -SkipTestsScript: 08-build-installer.ps1
What it does:
- Compiles installer using Inno Setup
- Generates
ZPL2PDF-Setup-{Version}.exe - Supports multiple languages
Generated files:
installer/Output/ZPL2PDF-Setup-{Version}.exe(also copied torelease/)release/ZPL2PDF-Setup-{Version}.exe
Execution:
.\scripts\release\08-build-installer.ps1 -Version "3.1.0"Script: 09-build-linux-packages.ps1
What it does:
- Generates
.debpackage (Debian/Ubuntu) - Generates
.rpmpackage (Red Hat/CentOS) - Uses Docker to ensure compatibility
Generated files:
release/ZPL2PDF-v{Version}-linux-amd64.debrelease/ZPL2PDF-v{Version}-linux-x64-rpm.tar.gz
Execution:
.\scripts\release\09-build-linux-packages.ps1 -Version "3.1.0"Script: 10-update-manifests.ps1
What it does:
- Updates version in all WinGet manifests
- Calculates installer SHA256
- Updates URLs and dates
Modified files:
manifests/brunoleocam.ZPL2PDF.yamlmanifests/brunoleocam.ZPL2PDF.installer.yamlmanifests/brunoleocam.ZPL2PDF.locale.*.yaml
Execution:
.\scripts\release\10-update-manifests.ps1 -Version "3.1.0"Script: 11-build-docker-images.ps1
What it does:
- Builds Docker image (Alpine Linux)
- Creates tags:
latest,{version},{major}.{minor},{major},alpine - Prepares for publication
Generated files:
- Local Docker images (not published yet)
Execution:
.\scripts\release\11-build-docker-images.ps1 -Version "3.1.0"Script: 12-create-version-branch.ps1
What it does:
- Creates branch
release/v{Version} - Commits all changes
- Creates Pull Request to
main - Does NOT auto-merge (requires manual approval)
Execution:
.\scripts\release\12-create-version-branch.ps1 -Version "3.1.0"Script: 13-create-github-release.ps1
What it does:
- Creates Git tag
v{Version} - Creates GitHub release
- Generates source code archives (ZIP and TAR.GZ)
- Uploads all generated files from
release/:- Builds for all platforms
- Windows installer
- Linux packages (.deb and .rpm)
- Source code archives
- Checksums (SHA256SUMS.txt)
Prerequisites:
- Git tag must exist or will be created
- GitHub CLI authenticated
Execution:
.\scripts\release\13-create-github-release.ps1 -Version "3.1.0"Script: 14-publish-dockerhub.ps1
What it does:
- Pushes Docker images to Docker Hub
- Publishes tags:
latest,{version},{major}.{minor},{major},alpine
Prerequisites:
- Docker Hub authenticated:
docker login - Docker images already built (Step 11)
Execution:
.\scripts\release\14-publish-dockerhub.ps1 -Version "3.1.0"Script: 15-publish-ghcr.ps1
What it does:
- Pushes Docker images to GHCR (ghcr.io)
- Publishes tags:
latest,{version},{major}.{minor},{major},alpine
Prerequisites:
- GitHub CLI authenticated with
write:packagesscope - Docker images already built (Step 11)
Execution:
.\scripts\release\15-publish-ghcr.ps1 -Version "3.1.0"Script: 16-submit-winget-pr.ps1
What it does:
- Updates fork of
microsoft/winget-pkgsrepository - Creates branch with new version
- Copies updated manifests
- Creates Pull Request to
microsoft/winget-pkgs
Prerequisites:
- GitHub CLI authenticated
- Fork of
microsoft/winget-pkgsexists - Manifests updated (Step 10)
- GitHub release created (Step 13)
Execution:
.\scripts\release\16-submit-winget-pr.ps1 -Version "3.1.0"-
Preparation (Steps 1-6)
- Update documentation
- Update resources and i18n
- Update CHANGELOG
- Update Dockerfile
-
Build (Steps 7-9)
- Build all platforms
- Build Windows installer
- Build Linux packages
-
Publication Preparation (Steps 10-11)
- Update manifests
- Build Docker images
-
Git and GitHub (Steps 12-13)
- Create version branch and PR
- Create GitHub release
-
Publication (Steps 14-16)
- Publish to Docker Hub
- Publish to GHCR
- Submit WinGet PR
# Run complete release
.\scripts\release\release-main.ps1 -Version "3.1.0"All scripts accept the following parameters:
| Parameter | Type | Description | Default |
|---|---|---|---|
-Version |
string | Version to release (required) | - |
-DryRun |
switch | Runs without making permanent changes | $false |
-SkipTests |
switch | Skips test execution | $false |
-SkipDocker |
switch | Skips Docker-related steps | $false |
-SkipWinGet |
switch | Skips WinGet submission | $false |
-SkipGitHubRelease |
switch | Skips GitHub release creation | $false |
-Resume |
switch | Resumes release skipping already completed steps | $false |
-StartFromStep |
int | Starts from a specific step (1-16) | 1 |
Solution: Install the missing tool or use -Skip* parameters to skip steps
Solution:
- GitHub CLI:
gh auth login - Docker Hub:
docker login
Solution:
- Check if code compiles:
dotnet build - Run tests:
dotnet test - Check error logs
- Use
-Resumeto resume from where it stopped
Solution: Start Docker Desktop
Solution:
- Use a different version
- Or delete the existing release on GitHub
Solution:
# Check checkpoint status
# (shown automatically when using -Resume)
# Resume from where it stopped
.\scripts\release\release-main.ps1 -Version "3.1.0" -Resume
# Or continue from a specific step
.\scripts\release\release-main.ps1 -Version "3.1.0" -StartFromStep 8Solution:
# Delete checkpoint and start from scratch
Remove-Item .release-checkpoint-3.1.0.json
.\scripts\release\release-main.ps1 -Version "3.1.0"Before starting, verify:
- Version defined (e.g., 3.1.0)
- CHANGELOG.md updated with changes
- Code tested and working
- All tools installed
- All accounts authenticated
- Docker Desktop running
-
mainbranch updated - No uncommitted changes
During execution:
- Steps 1-6 completed (preparation)
- Builds generated correctly (Step 7)
- Installer generated (Step 8)
- Linux packages generated (Step 9)
- Manifests updated (Step 10)
- Docker images built (Step 11)
- PR created on GitHub (Step 12)
- Release created on GitHub (Step 13)
- Docker Hub published (Step 14)
- GHCR published (Step 15)
- WinGet PR submitted (Step 16)
After release:
- PR approved and merged to
main - Release available on GitHub
- Docker images available
- WinGet PR approved (may take days)
- Announce release
- Always test with
-DryRunfirst - Run steps individually if there's a failure
- Verify each step before proceeding
- Keep logs of each release
- Document issues found
- Use semantic versioning (SemVer)
Last updated: December 2025
Guide version: 1.0.0