This repository automates Portable Version builds for HagiCode Desktop by resolving upstream Desktop and Server packages from the official index manifests, downloading indexed raw assets through Azure Blob SAS URLs, injecting the fixed portable payload, repacking the archive, and publishing the final Portable Version release into the dedicated Azure Blob container hagicode-steam.
Portable builds also bundle a pinned Node.js runtime and a preinstalled OpenSpec CLI so the unpacked archive can run node, openspec, and opsx without depending on machine-wide installations.
Packaging and Steam publication use separate workflows:
.github/workflows/portable-version-build.yml(portable-version-release) handles build-plan resolution, packaging, Azure publication, and Portable Version root-index refresh..github/workflows/portable-version-steam-release.yml(portable-version-steam-release) manually publishes an existing Azure-hosted Portable Version release to Steam.
The release workflow supports three non-interactive entrypoints:
schedulepolls the Desktop and Server index manifests on a daily cadence.workflow_dispatchsupports targeted rebuilds with optional Desktop selector, service selector, platform, dry-run, and force-rebuild inputs.repository_dispatchacceptsclient_payloadfields (serviceTag, optionaldesktopTag,platforms,forceRebuild,dryRun).serviceTagis required so the automation stays non-interactive, whiledesktopTagfalls back to the default Desktop resolution path when omitted.
The Steam workflow is workflow_dispatch only and requires an explicit release tag for every run.
portable-version-release accepts these workflow_dispatch inputs:
desktop_tag: optional Desktop version selector.refs/tags/v0.1.34,v0.1.34, and0.1.34are normalized to the same selector.service_tag: optional Server version selector.refs/tags/v0.1.0-beta.35,v0.1.0-beta.35, and0.1.0-beta.35are normalized to the same selector. In the release-tag convention described below, this current service payload version is treated as the "Web" component.platforms: comma-separated platforms. Supported values arelinux-x64,win-x64,osx-universal,osx-x64,osx-arm64, orall.force_rebuild: keep packaging even if the derived Portable Version Azure release already exists.dry_run: skip Azure publication while still resolving, staging, and packaging.
When no selector is provided, the build plan resolves the latest indexed Desktop version and the latest indexed Server version.
portable-version-steam-release accepts these workflow_dispatch inputs:
release: required Portable Version release tag to hydrate and publish to Steam. The workflow does not infer "latest".steam_preview: generate a Steam preview build instead of publishing and setting the beta branch live.steam_branch: Steam branch to set live for non-preview uploads. This defaults tobeta.steam_description: optional Steam build description override.
Portable Version releases use a readable Web-driven tag:
- canonical format:
<web-tag> - current mapping:
web-tagmeans the selectedservice_tag/PCode.Webpayload version desktop_tagremains available as an optional source-selection override, but it only affects Desktop asset resolution and provenance- normalization rule:
refs/tags/v0.1.0-beta.35,v0.1.0-beta.35, and0.1.0-beta.35all normalize tov0.1.0-beta.35 workflow_dispatchexample:service_tag=0.1.0-beta.35plusdesktop_tag=refs/tags/v0.1.34still produces release tagv0.1.0-beta.35repository_dispatchexample:{"event_type":"portable-version-build","client_payload":{"serviceTag":"0.1.0-beta.35"}}resolves the latest Desktop release and still producesv0.1.0-beta.35
The same Web-only tag is reused for duplicate detection, Azure version directories, root-index entries, dry-run metadata filenames, and Steam hydration.
Portable Version uses three Azure-backed data surfaces:
- Desktop index:
https://index.hagicode.com/desktop/index.json - Server index:
https://index.hagicode.com/server/index.json - Portable Version publication container:
hagicode-steam
The resolve step reads the Desktop and Server manifests, picks the selected version entries, and records the matched platform assets. Packaging then downloads the raw archives by combining:
- the asset
pathfrom the index manifest - the Desktop Azure Blob SAS container URL from
PORTABLE_VERSION_DESKTOP_AZURE_SAS_URL - the Server Azure Blob SAS container URL from
PORTABLE_VERSION_SERVICE_AZURE_SAS_URL
The publication step writes these assets into hagicode-steam/<releaseTag>/:
- Portable Version platform archives such as
hagicode-portable-linux-x64.zip <releaseTag>.build-manifest.json<releaseTag>.artifact-inventory.json<releaseTag>.checksums.txt
After the versioned blobs are visible, the workflow refreshes hagicode-steam/index.json. Each version entry in that root index carries:
versionmetadata.buildManifestPathmetadata.artifactInventoryPathmetadata.checksumsPathsteamDepotIds.linuxsteamDepotIds.windowssteamDepotIds.macosartifacts[]with per-platform blob-relative paths
Steam publication now treats hagicode-steam/index.json as the only source of truth for hydration and depot resolution.
Recommended repository secrets:
PORTABLE_VERSION_DESKTOP_AZURE_SAS_URL: Desktop Azure Blob container SAS URL with at leastReadandListpermissions.PORTABLE_VERSION_SERVICE_AZURE_SAS_URL: Server Azure Blob container SAS URL with at leastReadandListpermissions.PORTABLE_VERSION_STEAM_AZURE_SAS_URL: Azure Blob SAS URL for thehagicode-steamcontainer. The release workflow needsRead,List,Write, andCreate; the Steam workflow only needsReadandList.STEAM_DEPOT_ID_LINUX: depot id for Linux builds. The release workflow writes this into the root index and the Steam workflow consumes it from there.STEAM_DEPOT_ID_WINDOWS: depot id for Windows builds.STEAM_DEPOT_ID_MACOS: depot id for the unified macOS build.STEAM_APP_ID: Steam app id for the Desktop product.STEAM_USERNAME: Steam build account name.STEAM_PASSWORD: Steam build account password.STEAM_SHARED_SECRET: optional Steam Guard shared secret for fully unattended uploads.STEAM_GUARD_CODE: optional fallback Steam Guard code when a shared secret is not available.
Optional repository variables:
PORTABLE_VERSION_STEAMCMD_ROOT: absolute path on the self-hosted runner where SteamCMD and its persistentconfig/config.vdfshould be stored. Defaults to$HOME/.local/share/portable-version/steamcmd.
Workflow permissions are set to:
portable-version-release:contents: write,actions: readportable-version-steam-release:contents: read
The Steam workflow is pinned to a dedicated self-hosted runner with labels self-hosted, Linux, X64, and steam.
The automation currently assumes:
- scheduled builds default to the full platform matrix:
linux-x64,win-x64, andosx-universal - Desktop assets are selected from index
assets[]by platform-specific naming rules. Linux prefers zip fixtures when present and otherwise falls back to the indexed AppImage; Windows uses the published*-unpacked.zip; macOS uses the published zip archives. - Server assets follow the framework-dependent naming contract used by HagiCode releases, for example
hagicode-0.1.0-beta.35-linux-x64-nort.zip. - the selected Server asset extracts to a structure that contains
manifest.json,config/,lib/PCode.Web.dll,lib/PCode.Web.runtimeconfig.json, andlib/PCode.Web.deps.json. - the downloaded Desktop asset already contains
resources/extra/portable-fixed/orContents/Resources/extra/portable-fixed/, and the workflow injects the runtime intocurrent/inside that directory. - the portable toolchain manifest is defined in
config/portable-toolchain.json, which pins the Node.js distribution per platform and the bundled OpenSpec CLI package version. - the repacked archive stages the portable toolchain under
portable-fixed/toolchain/, includingnode/,npm-global/,bin/openspec,bin/opsx,env/activate.*, andtoolchain-manifest.json.
Steam publication hydrates its input from an existing Azure-hosted Portable Version release instead of package-job artifacts or GitHub Release assets. portable-version-steam-release now:
- validates the required
releaseinput againsthagicode-steam/index.json - downloads the Azure-hosted build manifest, artifact inventory, and checksums referenced by the matched version entry
- downloads each published Portable Version archive referenced by the root index and artifact inventory
- reconstructs
steam-content/<platform>from those archives, usingsteam-content/osx-universalfor the unified macOS depot when available - installs
steamcmdon the dedicated self-hosted runner - generates app and depot VDF scripts under
steam-build/scripts/ - saves the initial SteamCMD login token under the persistent SteamCMD root and reuses that token on future runs
- derives a Steam Guard code from
STEAM_SHARED_SECRETwhen available, otherwise usesSTEAM_GUARD_CODEif provided - runs
steamcmd +run_app_buildin preview or publish mode
steam_preview=false uploads the build while setting beta live unless you override steam_branch. steam_preview=true keeps the Steam upload in preview mode so you can validate depot mappings and authentication without pushing a live update; preview runs do not pass setlive even if steam_branch is populated.
If the selected release is missing the build manifest, merged artifact inventory, depot mapping, or one of the required platform archives, the workflow fails before any Steam login happens. That usually means the Azure root index entry is incomplete or the Azure version directory is only partially published and should be republished first.
Run the helper tests from the repository root for portable-version:
npm test
npm run verify:dry-runThe dry-run test uses fixture assets and validates Desktop archive preparation, Server payload extraction, toolchain staging, archive repacking, and Azure publication planning without writing to the real container.
For manual local staging you can override the network download step with fixture files:
scripts/prepare-packaging-workspace.mjs --desktop-asset-source <file-or-url>scripts/stage-portable-payload.mjs --service-asset-source <file-or-url>
Those overrides are intended for tests and diagnostics only. Production packaging must use index asset.path + PORTABLE_VERSION_DESKTOP_AZURE_SAS_URL or asset.path + PORTABLE_VERSION_SERVICE_AZURE_SAS_URL, depending on the asset source.
Portable Version publication no longer treats GitHub Release as a source of truth.
Removed assumptions:
- Portable Version release hydration from GitHub Release assets
- GitHub Release duplicate detection for the primary build workflow
- DLC root-index lookup for the main application's Steam depot mappings
- repository-level
STEAM_DEPOT_ID_*secrets at Steam publication time
If you have external tooling that consumed GitHub Release assets, migrate it to:
hagicode-steam/index.jsonhagicode-steam/<releaseTag>/<releaseTag>.build-manifest.jsonhagicode-steam/<releaseTag>/<releaseTag>.artifact-inventory.jsonhagicode-steam/<releaseTag>/<releaseTag>.checksums.txthagicode-steam/<releaseTag>/<portable-archive>.zip
Use these recovery paths when a workflow run fails or must be replayed:
- Re-run
portable-version-releasewithdry_run=trueto confirm index resolution, payload staging, repacking, and Azure publication planning without writing blobs. - If the derived Portable Version Azure version directory already exists but the prior upload was partial, re-run with
force_rebuild=true. - If a specific upstream build must be replayed, provide
service_tagand optionallydesktop_tagwhen you need to pin a non-default Desktop asset. - Inspect the uploaded workflow artifacts:
portable-release-build-planportable-release-package-<platform>portable-release-metadata-<release-tag>portable-steam-release-preparation-<release-tag>portable-steam-build-metadata-<release-tag>
- Review the workflow summary for the exact selector mismatch, Azure upload failure, root-index refresh failure, archive hydration failure, Steam authentication issue, or SteamCMD publication error.
Each successful build publishes:
- one deterministic Portable Version version directory in the
<web-tag>namespace underhagicode-steam/ - repacked Desktop artifacts copied to deterministic asset names such as
hagicode-portable-linux-x64.zip - the normalized build manifest
- merged artifact inventory metadata
- merged SHA-256 checksums
- one root-index entry containing
metadata.*,steamDepotIds.*, andartifacts[] - one toolchain validation report per platform, proving the bundled
node,openspec, andopsxcommands executed successfully before publication