steam_packer is an independent packaging and Azure publication repository.
It accepts a normalized release plan from any non-interactive caller, assembles the packaging workspace, stages the payload, validates the Desktop-authored bundled toolchain, repacks deterministic platform archives, uploads them to Azure Blob Storage, and refreshes the root hagicode-steam/index.json entry without changing the downstream Steam hydration contract.
- Owns Portable Version packaging-safe workspace assembly
- Owns archive generation, artifact inventory, and checksum emission
- Owns Azure Blob publication and root-index refresh
- Exposes reusable CI and CLI entrypoints for external callers
- Keeps release metadata compatible with downstream Steam hydration consumers
portable-version is currently one caller, but steam_packer is not coupled to that repository.
The primary input is a build-plan JSON artifact. The caller is responsible for producing it.
The plan must contain:
release.tagplatforms[]platformMatrix.include[]upstream.desktop.*upstream.service.*downloads.desktop.containerUrldownloads.service.containerUrlbuild.shouldBuildbuild.forceRebuildbuild.dryRunenvConfig(optional; defaults to{ "HAGICODE_MODE": "steam", "HAGICODE_STEAM_ACHIEVEMENT_SYNC_ENABLED": "true" })handoff.schema = steam-packer-handoff/v1
steam_packer validates the release plan before any workspace mutation. Missing fields fail fast at the build-plan-validation stage.
envConfig rules:
- Only uppercase
HAGICODE_*keys are accepted. - Every value must be a single-line string without NUL bytes.
HAGICODE_MODEis always normalized tosteam.HAGICODE_STEAM_ACHIEVEMENT_SYNC_ENABLEDis always present, defaults totrue, and must betrueorfalsewhen provided.prepare-packaging-workspace.mjswrites the normalized config tohagicode.envbeside the packaged executable and records bothenvConfigandenvFilePathinworkspace-manifest.json.
Reusable workflow:
.github/workflows/package-release.yml
Scheduled trigger behavior:
package-releaseruns automatically every 4 hours withcron: 0 */4 * * *.- Scheduled runs auto-resolve the latest Desktop index release and the latest Service index release.
- Scheduled runs use the default publication targets:
linux-x64,win-x64, andosx-universal. - The derived Portable Version release tag is checked before packaging. If that release already exists and no manual override is present,
build.shouldBuild=falseand the package/publish jobs are skipped. - The Actions summary records the trigger type, latest upstream versions, derived release tag,
should_build, and the skip reason when packaging is skipped.
Expected caller behavior:
- Produce a normalized build plan and upload it as an artifact.
- Call the reusable workflow in
steam_packer. steam_packervalidates the plan and fans out packaging by platform.steam_packerpublishes merged metadata and refreshes the Azure root index.
Manual trigger behavior:
workflow_dispatchis supported for maintainers.- Manual dispatch auto-resolves the latest Desktop index release and the latest Service index release.
- Manual dispatch defaults to the three supported publication targets:
linux-x64,win-x64, andosx-universal. - Manual dispatch exposes
force_rebuild,dry_run, and an optionalenv_configJSON string; it does not require a build-plan parameter. - Set
force_rebuild=trueto package and publish even when the derived Portable Version release already exists. - Set
dry_run=trueto run packaging and emit release metadata without writing to the Azure Steam container. - Set
env_configto a JSON object such as{"HAGICODE_LOG_LEVEL":"info","HAGICODE_DEBUG":"false"}when a Steam package needs additional runtime flags. Unsupported keys fail validation before workspace preparation. - Manual runs still require the same Azure SAS secrets as reusable runs.
From repos/steam_packer:
npm test
npm run verify:dry-run
npm run verify:publication
npm run verify:release-planFor fixture-driven diagnostics:
node scripts/run-release-plan.mjs \
--plan /path/to/build-plan.json \
--desktop-asset-source /path/to/desktop.zip \
--service-asset-source /path/to/service.zip \
--force-dry-runFor non-interactive plan generation from the steam_packer repository itself:
node scripts/resolve-dispatch-build-plan.mjs \
--event-name workflow_dispatch \
--desktop-azure-sas-url "<desktop-sas>" \
--service-azure-sas-url "<service-sas>" \
--env-config '{"HAGICODE_LOG_LEVEL":"info"}' \
--output build/build-plan.jsonThe generated plan carries:
{
"envConfig": {
"HAGICODE_MODE": "steam",
"HAGICODE_STEAM_ACHIEVEMENT_SYNC_ENABLED": "true",
"HAGICODE_LOG_LEVEL": "info"
}
}and the prepared workspace emits:
HAGICODE_MODE=steam
HAGICODE_STEAM_ACHIEVEMENT_SYNC_ENABLED=true
HAGICODE_LOG_LEVEL=infosteam_packer does not download Node, install OpenSpec, or assemble the portable toolchain. The Desktop asset is the owner of extra/toolchain; this repository only verifies the Desktop-authored toolchain-manifest.json contract and packages the validated input.
Result stages are attributed as:
build-plan-validationdelegated-packagingazure-publication
steam_packer preserves the existing publication layout:
hagicode-steam/<releaseTag>/<archive>.ziphagicode-steam/<releaseTag>/<releaseTag>.build-manifest.jsonhagicode-steam/<releaseTag>/<releaseTag>.artifact-inventory.jsonhagicode-steam/<releaseTag>/<releaseTag>.checksums.txthagicode-steam/index.json
The root index entry remains compatible with current Steam hydration consumers:
versionmetadata.buildManifestPathmetadata.artifactInventoryPathmetadata.checksumsPathsteamAppIdsteamDepotIds.linuxsteamDepotIds.windowssteamDepotIds.macosartifacts[]
Publication now resolves both steamAppId and steamDepotIds from the shared Steam dataset:
- default
steamAppKey:hagicode - optional override:
--steam-app-key,STEAM_PACKER_STEAM_APP_KEY, orSTEAM_APP_KEY --steam-data-pathorSTEAM_PACKER_STEAM_DATA_PATH- By default,
steam_packerdownloads the dataset fromhttps://index.hagicode.com/steam/index.json. --steam-data-path/STEAM_PACKER_STEAM_DATA_PATHremain compatibility overrides, and can point to either a local JSON file or an explicithttps://...URL.
steam_packer resolves the canonical steamAppId from applications[].key and maps applications[].platformAppIds to steamDepotIds. The resolved values are reused for dry-run output, publication result metadata, and every versions[] entry written back to hagicode-steam/index.json. Legacy entries that predate steamAppId are backfilled from the same resolved value, while any conflicting non-empty steamAppId still aborts the write.
The current Portable Version integration is documented in docs/portable-version-migration.md.