Unity
A Unity project keeps its version in ProjectSettings/ProjectSettings.asset and its dependencies in
Packages/manifest.json. dispat reads and writes both. A Unity game releases the same way an npm package does: one
command, one version, one tag.
The short version
{
"scripts": {
"build": "Unity -quit -batchmode -projectPath game -executeMethod Builder.Build",
"publish": "butler push game/Build acme/game:windows --userversion $DISPAT_NEW_VERSION"
},
"packages": {
"game": {
"path": "game",
"flow": {"build": "build", "publish": "publish"},
"autoVersion": {"enabled": true, "manifests": "all"}
}
},
"initials": {"game": "0.1.0"}
}
Use manifests: all to reach ProjectSettings/ProjectSettings.asset. The file sits one folder down from the package
rather than directly in it. Commit a feature and run dispat. The settings file, the git tag, and the changelog will
all show the same version.
$ git commit -m "feat(game): co-op lobby"
$ dispat
12:50:45 INF ● changed bump=minor package=game version="0.1.0 -> 0.2.0"
12:50:45 INF manifest reconciled manifest=ProjectSettings/ProjectSettings.asset package=game ranges=0 stage=version version=0.2.0 versionWritten=true
12:50:45 INF published package=game tag=game@0.2.0 version=0.2.0
What dispat reads
ProjectSettings/ProjectSettings.asset
| Field | Read as | Written by |
|---|---|---|
productName | the package's name | never |
bundleVersion | the version | --set-version, and auto-versioning |
AndroidBundleVersionCode | the build counter | --set-build |
buildNumber (per platform) | the build counter, when there is no Android one | --set-build |
The file is YAML, but standard libraries cannot parse it. Unity writes its own tag directive and tags the document with a class id.
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!129 &1
PlayerSettings:
productName: Acme Game
bundleVersion: 1.2.0
buildNumber:
Standalone: 7
iPhone: 7
AndroidBundleVersionCode: 7
A conforming YAML parser refuses !u!129. dispat reads the file by line instead, so it can parse any real Unity
project. It only reads settings one level deep, so nested mappings cannot leak a version into the answer.
Packages/manifest.json
Every entry is a dependency. dispat accepts all three forms Unity uses:
{
"dependencies": {
"com.unity.textmeshpro": "3.0.6",
"com.acme.core": "file:../../packages/core",
"com.acme.tools": "https://github.com/acme/tools.git#v1.2.3"
}
}
dispat keeps all three exactly as written. The file: form tells dispat which folder it points at. This makes an
embedded package part of the workspace graph. Release com.acme.core, and the project consuming it releases next.
The manifest declares no name and no version of its own. It lists what the project consumes.
Build numbers for the stores
Google Play and the App Store order uploads by an integer counter. They ignore the version players see. Run
--set-build to write that counter and nothing else:
$ dispat writer --set-build "$GITHUB_RUN_NUMBER" game/ProjectSettings/ProjectSettings.asset
Every counter in the file moves. A project shipping to Steam, the App Store, and Google Play from one settings file has three counters. Stamping only one of them would upload two builds with the wrong store order.
Pass an integer for the counter. The stores require it, so dispat enforces it. Passing a version string fails before the file opens, saving you from an upload error later.
A version write never touches a counter. A build write never touches the version. They move for different reasons, so you use separate commands for each.
Unity packages
A UPM package is an npm package. It uses the same package.json, the same fields, and the same registry protocol.
dispat reads it with its npm reader. A repository of Unity packages needs no special setup.
Set the range policy to exact. UPM resolves an exact version and nothing else. A caret range leaves a project that fails to open:
{
"packages": {
"core": {
"path": "packages/core",
"autoVersion": {"enabled": true, "range": "exact"}
}
}
}
Ranges written into Packages/manifest.json are always pinned. dispat ignores the policy here to keep the project
working.
What dispat leaves alone
ProjectSettings/ProjectVersion.txt pins the editor version. This is a toolchain choice. dispat never reads or writes
it.
Packages/packages-lock.json is a lock file. dispat does not write lock files in any ecosystem. Run the tool that owns
yours in the syncLock hook.
The folders dispat stays out of
Library/ is Unity's node_modules. Its PackageCache holds a real package.json for every resolved package, which
would flood your workspace with third-party packages if scanned. dispat never descends into Library/, Temp, Logs,
UserSettings, MemoryCaptures, or Builds.
Rename any source folder called Library or Builds. dispat will not find manifests inside them.
Where to go next
- Read From one package to many when your repository grows past one project.
- See Godot and Unreal for the other engines.
- Read Auto-versioning to see what
manifestsandrangedo in full.
Validate every distribution format
A UPM package, a Unity package export, and a NuGet package can carry the same source but have different publication steps. Keep the native UPM manifest, test each artifact as a consumer would, and reconcile successful destinations before retrying. See integration findings.
Check identity at all three boundaries: the package.json version, the release tag, and the version embedded in each
download name or registry record. A project may publish one source version as a Git-installable UPM package, a
.unitypackage, and a NuGet package. Success at one destination says nothing about the others, so reconcile each
publication before a retry and install each fresh artifact in a clean consumer project. A NuGet integration tool does
not necessarily publish itself to NuGet; follow the declared distribution policy.
Android exports can include native libraries from packages, plugins and middleware. Apply the engine-independent
Android alignment check to every 64-bit .so in the
final artifact, including wrappers. Keep ELF, APK ZIP and device checks separate.