This checklist tracks the current desktop packaging path. It is intentionally explicit about what is automated now, what release owners must provide through secrets, and what remains deferred.
The next public desktop build should target:
- Windows x64 portable zip containing
PixelAid.exe, license files, notices, and package notes. - macOS zipped
.appbundle containingPixelAid.app, license files, notices, and package notes.
Build each platform on its native release machine or CI runner. Do not cross-sign desktop artifacts locally. Installer, DMG, Linux, Microsoft Store, Mac App Store, and auto-update artifacts are deferred until the direct-download zip flow is stable.
Before tagging a release, update all package and desktop metadata from the repo root:
npm run version:set -- 0.2.0You can also use patch, minor, or major in place of an exact version. The command keeps workspace packages, internal @pixelaid/* dependency versions, package-lock.json, apps/desktop/src-tauri/Cargo.toml, and apps/desktop/src-tauri/tauri.conf.json aligned.
Unsigned local builds are allowed for development and smoke testing:
npm install
npm run desktop:check
npm run desktop:build
npm run desktop:packagenpm run desktop:check verifies Node, npm, Rust/Cargo, and the Tauri CLI before packaging starts. npm run desktop:build runs that prerequisite check and then calls Tauri's packaging command from apps/desktop.
npm run desktop:package builds an unsigned zip for the current platform. Run npm run desktop:package:windows on Windows to create artifacts/desktop/PixelAid-<version>-windows-x64-portable.zip. Run npm run desktop:package:macos on macOS to create artifacts/desktop/PixelAid-<version>-macos-<arch>-app.zip. The generated artifacts/desktop/ directory is ignored by git.
The Windows portable executable is built as a GUI app, so it should not open a console window when launched normally. The macOS artifact is a normal .app bundle inside a zip; opening it from Finder should not open Terminal. The bundle is named PixelAid.app, while the internal executable is read from PixelAid.app/Contents/Info.plist and may use the Cargo package name, such as pixelaid-desktop.
Unsigned desktop packages remain the default. Signed Windows packaging is opt-in:
npm run desktop:package:windows:signedThe signed command reads local Azure Artifact Signing values from .env in the repo root, then merges the current shell environment over those values. The .env file is ignored by git. Do not commit signing metadata that identifies a real account, private keys, credentials, or generated release artifacts.
Use .env.example as the shape for local setup:
WINDOWS_SIGNING_ENDPOINT="https://REGION.codesigning.azure.net"
WINDOWS_SIGNING_ACCOUNT_NAME="YOUR_ARTIFACT_SIGNING_ACCOUNT"
WINDOWS_SIGNING_CERTIFICATE_PROFILE_NAME="YOUR_CERTIFICATE_PROFILE"The signed Windows command builds the Tauri executable, copies it into artifacts/desktop/staging/, signs the staged PixelAid.exe with SignTool and the Azure Artifact Signing dlib, verifies the Authenticode signature, and finally creates artifacts/desktop/PixelAid-<version>-windows-x64-signed-portable.zip.
Local prerequisites:
- Windows SDK SignTool
10.0.2261.755or newer. - .NET 8 runtime for the Artifact Signing dlib.
Microsoft.ArtifactSigning.ClientNuGet package available under the user NuGet package cache orartifacts/tools/.- An Azure login method supported by
DefaultAzureCredential; Azure CLI is the simplest local path. Artifact Signing Certificate Profile Signerpermission for the certificate profile.
Example local Azure CLI setup:
winget install -e --id Microsoft.AzureCLI
az loginThe package script auto-discovers signtool.exe from the Windows SDK and Azure.CodeSigning.Dlib.dll from common NuGet package locations. If auto-discovery fails, set:
WINDOWS_SIGNING_SIGNTOOL_PATH="C:\\path\\to\\signtool.exe"
WINDOWS_SIGNING_DLIB_PATH="C:\\path\\to\\Azure.CodeSigning.Dlib.dll"The generated metadata excludes InteractiveBrowserCredential by default so local and CI signing cannot hang on a hidden browser prompt. Use Azure CLI, Azure PowerShell, Visual Studio, or another non-interactive DefaultAzureCredential source before running the signed package command. If an interactive browser flow is intentionally needed for a local test, set WINDOWS_SIGNING_ALLOW_INTERACTIVE_BROWSER=1.
Verify a signed package after extracting it:
node apps/desktop/scripts/verify-desktop-package.mjs windows C:\path\to\extracted\package --signedUnsigned desktop packages remain the default. Signed macOS packaging is opt-in:
npm run desktop:package:macos:signedThe signed command reads local Apple signing and notarization values from .env in the repo root, then merges the current shell environment over those values. The .env file is ignored by git. Do not commit certificates, private keys, API keys, app-specific passwords, or generated notarization archives.
Use .env.example as the shape for local setup:
APPLE_SIGNING_IDENTITY="Developer ID Application: Your Name (TEAMID)"
APPLE_API_KEY="YOUR_KEY_ID"
APPLE_API_ISSUER="YOUR_ISSUER_ID"
APPLE_API_KEY_PATH="$HOME/.appstoreconnect/private_keys/AuthKey_YOUR_KEY_ID.p8"The signed macOS command builds the Tauri .app without Apple signing variables in the build environment, copies the app into artifacts/desktop/staging/, signs the staged copy with Developer ID and hardened runtime, submits it to Apple notarization with notarytool, staples the notarization ticket, runs local signature/Gatekeeper checks, and finally creates artifacts/desktop/PixelAid-<version>-macos-<arch>-signed-app.zip.
Verify a signed package after extracting it:
node apps/desktop/scripts/verify-desktop-package.mjs macos /path/to/extracted/package arm64 --signedUse x64 instead of arm64 for Intel builds.
Release and artifact workflows read opt-in telemetry build settings from GitHub Actions secrets and variables. Add these under Settings -> Secrets and variables -> Actions before running release-candidate workflows.
Secrets:
TELEMETRY_POSTHOG_PROJECT_KEYVariables:
TELEMETRY_PROVIDER=posthog
TELEMETRY_POSTHOG_HOST=https://us.i.posthog.com
TELEMETRY_ENABLED=1
TELEMETRY_BUILD_CHANNEL=releaseThe workflow YAML sets per-artifact distribution labels. Do not add those in the GitHub UI. The current labels are:
desktop_windows_portable
desktop_macos_app
web_standalone
web_itchThe PostHog project key is shipped in client-side web and desktop bundles by design. Treat it as public client configuration, but keep it in GitHub Secrets so workflow logs do not casually print it. Users still need to opt in from File -> Privacy & Telemetry before telemetry events are sent.
Signed Windows release-candidate artifacts use the release-signing GitHub Environment and Azure Artifact Signing with OpenID Connect. Do not store Azure client secrets for this workflow.
Create a GitHub Environment named:
release-signingAdd these environment secrets under Settings -> Environments -> release-signing:
AZURE_CLIENT_ID
AZURE_TENANT_ID
AZURE_SUBSCRIPTION_ID
WINDOWS_SIGNING_ENDPOINT
WINDOWS_SIGNING_ACCOUNT_NAME
WINDOWS_SIGNING_CERTIFICATE_PROFILE_NAMEIn Azure, create or reuse a Microsoft Entra app registration for GitHub Actions, then add a federated credential for this repository using:
Entity type: Environment
Environment name: release-signing
Audience: api://AzureADTokenExchangeGrant that app registration/service principal the Artifact Signing Certificate Profile Signer role, or the equivalent Trusted Signing Certificate Profile Signer role if that is the name shown in the Azure portal. Prefer assigning the role at the certificate profile scope. If the portal does not expose IAM at the profile scope, assign it at the signing account scope.
The workflow installs the Microsoft Artifact Signing client into the GitHub runner's NuGet cache, logs into Azure through OIDC, signs the staged Windows executable, verifies the Authenticode signature, and uploads only the signed portable zip.
Signed macOS release-candidate artifacts use the same release-signing GitHub Environment. Add these environment secrets under Settings -> Environments -> release-signing:
MACOS_CERTIFICATE_P12_BASE64
MACOS_CERTIFICATE_PASSWORD
MACOS_KEYCHAIN_PASSWORD
APPLE_SIGNING_IDENTITY
APPLE_API_KEY
APPLE_API_ISSUER
APPLE_API_KEY_P8_BASE64The workflow imports the certificate into a temporary runner keychain, writes the App Store Connect API key to a temporary file, signs and notarizes the staged .app, verifies the signed package, uploads the zip artifact, and removes temporary signing files.
itch.io publishing uses the release-publishing GitHub Environment. Add:
Secret: BUTLER_API_KEY
Variable: ITCH_TARGETWhen publish_itch is enabled, the release workflow publishes:
html5: itch.io browser build.windows: signed Windows portable package.macos-arm64: signed Apple Silicon macOS package.macos-x64: signed Intel macOS package whenmacos_x64is enabled.
Publishing requires windows_signed and macos_signed to be enabled so public desktop channels receive signed artifacts.
If publishing fails after a release artifact run already built successfully, use the separate .github/workflows/publish-itch.yml workflow with the previous run ID. It downloads the existing artifacts and republishes the itch.io channels without rebuilding or resigning the desktop apps. After workflow file changes, start a new manual Publish itch.io run instead of rerunning an older failed job, because GitHub reruns use the original workflow commit.
The manual GitHub Actions workflow at .github/workflows/desktop-artifacts.yml is artifact-only. It runs on workflow_dispatch, builds unsigned Windows and macOS arm64 packages, verifies the package contents, and uploads the resulting zip files as workflow artifacts without wrapping each package in another artifact zip. It does not create a GitHub Release, sign binaries, notarize macOS builds, push to itch.io, or publish anything externally.
This workflow is intentionally kept as the quick unsigned desktop smoke-test path. It can be triggered on any branch from Actions -> Desktop Artifacts -> Run workflow.
The workflow currently emits:
pixelaid-windows-portable: Windows x64 portable package.pixelaid-macos-arm64-app: macOS package for Apple Silicon Macs, including M-series MacBooks.
For public repositories, standard GitHub-hosted runners are free and unlimited. For private repositories, the same workflow consumes the account's included Actions minutes and may incur usage charges after those minutes are exhausted.
To test it:
- Push the branch containing the workflow.
- In GitHub, open Actions.
- Choose Desktop Artifacts.
- Select Run workflow.
- Download
pixelaid-windows-portableandpixelaid-macos-arm64-appfrom the completed run as needed. - Inspect each zip and smoke test the app on the matching operating system.
Browser downloads from GitHub apply macOS quarantine metadata. For unsigned and unnotarized CI artifacts, Finder may report that PixelAid.app is damaged or corrupted even when the package built correctly. For trusted internal smoke tests only, unzip the macOS package, then remove quarantine before first launch:
xattr -dr com.apple.quarantine PixelAid.appDo not ask public users to do this. Public macOS artifacts should be Developer ID signed and notarized before distribution.
The manual GitHub Actions workflow at .github/workflows/release-artifacts.yml is the broader release-candidate artifact path. It runs on workflow_dispatch, gates the build with license, typecheck, test, and lint checks, then creates release-candidate artifacts for:
- itch.io HTML5 upload:
pixelaid-web-itch. - Windows x64 portable package:
pixelaid-windows-portable, orpixelaid-windows-signed-portablewhenwindows_signedis enabled. - macOS Apple Silicon app package:
pixelaid-macos-arm64-app, orpixelaid-macos-arm64-signed-appwhenmacos_signedis enabled. - Optional macOS Intel app package:
pixelaid-macos-x64-app, orpixelaid-macos-x64-signed-appwhenmacos_x64andmacos_signedare both enabled.
Standalone web packaging remains available locally through npm run web:package:standalone, but it is intentionally not part of release-candidate artifacts yet.
Use this workflow when preparing a versioned release candidate after running npm run version:set and pushing the release branch or tag. It does not publish a GitHub Release or upload anything outside GitHub Actions artifacts unless publish_itch is enabled.
To test it:
- Push the branch containing the workflow.
- In GitHub, open Actions.
- Choose Release Artifacts.
- Select Run workflow.
- Leave signing checkboxes off for a normal unsigned release-candidate run, or enable
windows_signedandmacos_signedfor signed desktop artifacts. - Leave
macos_x64off for the default Apple Silicon macOS package only, or enable it to also build the Intel package. - Leave
publish_itchoff for artifact review only, or enable it to publish the web and signed desktop channels to itch.io after the artifacts build. - If publishing needs to be retried without rebuilding, run Publish itch.io with the release workflow run ID.
- Download and inspect the web and desktop artifacts from the completed run.
- Smoke test each desktop artifact on the matching operating system.
For a dry-run release check without secrets, use:
npm run desktop:release:check -- --allow-unsignedThe --allow-unsigned flag is for local dry runs only. Public builds should run the same check without that flag. Local release checks read .env by default; use --no-env-file to validate only the current shell environment.
Pushing a tag that matches v*.*.* runs .github/workflows/release-artifacts.yml automatically. Tag runs force signed Windows, signed macOS arm64, signed macOS x64, and itch.io publishing. Manual checkbox behavior remains unchanged for workflow_dispatch runs.
The workflow checks that the tag matches the root package version, so v0.1.1 requires package.json to contain 0.1.1.
Recommended release flow:
- Create a
releases/<version>branch. - Run
npm run version:set <version>. - Commit the version changes and open a PR.
- Push the matching tag, for example
v0.1.1, from the same commit. - Wait for the Release Artifacts workflow to finish and smoke test the published packages.
- Merge the release branch after the tag build succeeds.
Before producing public desktop artifacts:
npm run license:check
npm run typecheck
npm run test
npm run lint
npm run build
npm run desktop:check
npm run desktop:release:check
npm run desktop:package:windows:signed
npm run desktop:package:macos:signed
npm run desktop:checksumsnpm run desktop:release:check fails when required signing/notarization environment variables are missing. Signing secrets must come from local secret storage or CI secrets. Never commit certificates, private keys, notarization credentials, update keys, or passwords.
Provide one of these signing paths. Azure Artifact Signing is the preferred local and CI path:
WINDOWS_SIGNING_ENDPOINT,WINDOWS_SIGNING_ACCOUNT_NAME, andWINDOWS_SIGNING_CERTIFICATE_PROFILE_NAMEfor Azure Artifact Signing.WINDOWS_SIGNING_CERT_PATHandWINDOWS_SIGNING_CERT_PASSWORDfor a certificate file managed outside git.WINDOWS_SIGNING_COMMANDfor an external signing command supplied by the release environment.
Provide:
APPLE_SIGNING_IDENTITY
And one notarization credential set:
- App Store Connect API:
APPLE_API_KEY,APPLE_API_ISSUER, andAPPLE_API_KEY_PATH.
The current local signed package command uses App Store Connect API credentials. Apple ID password fallback remains deferred so secrets do not need to be passed through command-line arguments.
Installer, DMG, Linux package, Microsoft Store, Mac App Store, auto-update, GitHub Release automation, and itch.io publication flows are deferred until the portable zip and .app release-candidate artifacts are stable. Add those paths as separate release phases so signing, notarization, store review, and external publishing can be verified independently.
Generated Tauri artifacts live under apps/desktop/src-tauri/target/ and stay ignored. Generated direct-download package zips live under artifacts/desktop/ and also stay ignored. Release owners should copy package zips into the external release system rather than committing them.
Generate checksums after the artifacts are final:
npm run desktop:checksumsFor a copied artifact directory:
npm run desktop:checksums -- --dir C:\path\to\release-artifactsThe checksum script writes a sorted SHA256SUMS.txt and skips pre-existing .sha256 files plus SHA256SUMS.txt itself.
- Record the commit SHA, operating system, Node/npm versions, Rust/Cargo versions, and Tauri CLI version.
- Run the release gate commands above.
- Launch the packaged app.
- Confirm the packaged app metadata uses the PixelAid icon from
apps/desktop/src-tauri/icons/. - Import a PNG or JPEG through the native open dialog.
- Run Auto Suggest and Fix on the sample asset.
- Export a ZIP bundle through the native save dialog.
- Confirm the ZIP contains the fixed PNG, manifest, palette files, validation report, frame sequence files when applicable, and selected engine sidecars.
- Confirm the web app still imports and exports through the browser path.
- Publish artifact checksums next to the release artifacts.
Generated app icons live under apps/desktop/src-tauri/icons/ and are committed. Regenerate them with npm run brand:sync whenever the source brand artwork changes.
Auto-update delivery is deferred. The 1.1.0 distribution path is manual release publishing with signed artifacts where supported, release notes, and SHA256SUMS.txt.
Before enabling in-app updates, add and document:
- Update feed hosting and rollback policy.
- Updater signing key storage through
TAURI_SIGNING_PRIVATE_KEYandTAURI_SIGNING_PRIVATE_KEY_PASSWORD. PIXELAID_UPDATE_ENDPOINTor equivalent channel-specific feed URLs.- Release channels for stable/beta/internal builds.
- QA coverage for update install, rollback, checksum/signature failure, and offline launch.