Repository navigation
186 lines (172 loc) · 8.15 KB
/
Copy pathrelease.yml
File metadata and controls
186 lines (172 loc) · 8.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# Steam release tooling validation (#894).
#
# WHY THIS WORKFLOW DOES NOT PUBLISH ANYTHING
# --------------------------------------------
# `steamcmd` normally needs an interactive Steam Guard login on first use per machine, and Steam
# publish credentials are exactly the kind of secret that should never sit in a public repo's CI
# environment — the same reasoning that keeps the Steamworks SDK itself off every runner (see
# steam-stub.yml and docs/ops/build.md). There is also no GameBuildPipeline output to publish:
# that pipeline runs inside the editor's GUI (Build Game panel), which a headless runner can't
# drive.
#
# So this job validates the *tooling* — scripts/steam/Publish-ToSteam.ps1 and its VDF templates —
# against a synthetic fixture, entirely with -WhatIf, and never installs or invokes steamcmd. It
# proves the template substitution is correct and the safety guards (the beta-branch-only
# default, the steam_appid.txt refusal) actually fire. A real publish stays a documented, human-run
# local action — see docs/ops/shipping.md#doing-a-real-publish.
#
# WHAT IT DOES NOT PROVE
# -----------------------
# Nothing about a real steamcmd upload, Valve's servers, or the Steamworks partner site. Live
# behaviour is manually verified by a human with Steam partner access, per the shipping doc.
name: Release (Steam tooling validation)
# Manual-dispatch only, per CLAUDE.md: publishing tooling is deliberately never triggered
# automatically, even for validation-only runs like this one.
on:
workflow_dispatch:
inputs:
app_id:
description: "Steam App ID to render the fixture VDFs with (any placeholder value works — nothing is uploaded)"
required: false
default: "480"
depot_id:
description: "Steam Depot ID to render the fixture VDFs with"
required: false
default: "481"
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
validate-publish-tooling:
name: Validate Publish-ToSteam.ps1 (dry run only)
runs-on: ubuntu-24.04
# SonarCloud (GitHub Actions script-injection rule): a `${{ inputs.* }}` expression
# substitutes TEXTUALLY into a run: block before the shell ever parses it — a
# workflow_dispatch input containing e.g. `"; rm -rf /` would inject straight into the
# script rather than being passed as an argument. Routed through job-level env: once
# here instead, and every step below reads $INPUT_APP_ID / $INPUT_DEPOT_ID (an
# environment variable expansion, not a textual substitution) rather than the `${{ }}`
# form directly in a run: block.
env:
INPUT_APP_ID: ${{ inputs.app_id }}
INPUT_DEPOT_ID: ${{ inputs.depot_id }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Build a synthetic GameBuildPipeline-shaped fixture
shell: bash
run: |
mkdir -p /tmp/fixture-good
cat > /tmp/fixture-good/game.manifest <<'EOF'
Game:
Name: FixtureGame
EngineVersion: "0.0.1"
BuildId: "0.0.1+cafef00d00"
EOF
# A second fixture that carries the dev-only App 480 affordance —
# Publish-ToSteam.ps1 must refuse to touch this one at all.
mkdir -p /tmp/fixture-with-appid
cp /tmp/fixture-good/game.manifest /tmp/fixture-with-appid/game.manifest
echo "480" > /tmp/fixture-with-appid/steam_appid.txt
- name: WhatIf run against a valid fixture renders both VDFs correctly
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$out = "/tmp/fixture-good-output"
$appId = $env:INPUT_APP_ID
$depotId = $env:INPUT_DEPOT_ID
pwsh scripts/steam/Publish-ToSteam.ps1 `
-BuildPath /tmp/fixture-good `
-AppId $appId `
-DepotId $depotId `
-BetaBranch beta `
-Description "CI validation run — never uploaded" `
-OutputDir $out `
-WhatIf
if ($LASTEXITCODE -ne 0) {
Write-Error "Publish-ToSteam.ps1 -WhatIf exited $LASTEXITCODE against a valid fixture — it should succeed."
exit 1
}
$appVdf = Get-Content -Raw (Join-Path $out "app_build_$appId.vdf")
$depotVdf = Get-Content -Raw (Join-Path $out "depot_build_$depotId.vdf")
foreach ($rendered in @($appVdf, $depotVdf)) {
if ($rendered -match '\{\{[A-Z_]+\}\}') {
Write-Error "Rendered VDF still contains an unsubstituted token: $($Matches[0])"
exit 1
}
}
# Whitespace-tolerant: match the VDF key/value pair regardless of the
# template's exact tab padding, which is a formatting detail this
# check shouldn't be coupled to. Built via concatenation (not string
# interpolation of a quote-containing pattern) to keep the embedded
# literal double-quotes unambiguous.
if ($appVdf -notmatch ('"appid"\s+"' + [regex]::Escape($appId) + '"')) {
Write-Error "app_build.vdf does not contain the expected appid line."
exit 1
}
if ($appVdf -notmatch '"setlive"\s+"beta"') {
Write-Error "app_build.vdf does not set the beta branch live."
exit 1
}
if ($depotVdf -notmatch ('"DepotID"\s+"' + [regex]::Escape($depotId) + '"')) {
Write-Error "depot_build.vdf does not contain the expected DepotID line."
exit 1
}
Write-Host "OK: both VDFs rendered with no leftover tokens and the expected App/Depot IDs and branch."
- name: The public/default branch guard refuses without -Force
shell: pwsh
run: |
pwsh scripts/steam/Publish-ToSteam.ps1 `
-BuildPath /tmp/fixture-good `
-AppId $env:INPUT_APP_ID `
-DepotId $env:INPUT_DEPOT_ID `
-BetaBranch public `
-OutputDir /tmp/fixture-good-output-public `
-WhatIf
if ($LASTEXITCODE -eq 0) {
Write-Error "Publish-ToSteam.ps1 accepted -BetaBranch public without -Force — the safety guard regressed."
exit 1
}
Write-Host "OK: -BetaBranch public was refused without -Force (exit $LASTEXITCODE)."
- name: A build folder carrying steam_appid.txt is refused
shell: pwsh
run: |
pwsh scripts/steam/Publish-ToSteam.ps1 `
-BuildPath /tmp/fixture-with-appid `
-AppId $env:INPUT_APP_ID `
-DepotId $env:INPUT_DEPOT_ID `
-BetaBranch beta `
-OutputDir /tmp/fixture-with-appid-output `
-WhatIf
if ($LASTEXITCODE -eq 0) {
Write-Error "Publish-ToSteam.ps1 accepted a build folder containing steam_appid.txt — the dev-App-ID guard regressed."
exit 1
}
Write-Host "OK: a build folder carrying steam_appid.txt was refused (exit $LASTEXITCODE)."
- name: A missing BuildPath / game.manifest is refused
shell: pwsh
run: |
pwsh scripts/steam/Publish-ToSteam.ps1 `
-BuildPath /tmp/does-not-exist `
-AppId $env:INPUT_APP_ID `
-DepotId $env:INPUT_DEPOT_ID `
-BetaBranch beta `
-OutputDir /tmp/fixture-missing-output `
-WhatIf
if ($LASTEXITCODE -eq 0) {
Write-Error "Publish-ToSteam.ps1 accepted a nonexistent BuildPath."
exit 1
}
Write-Host "OK: a nonexistent BuildPath was refused (exit $LASTEXITCODE)."
- name: steamcmd was never installed on this runner
shell: bash
run: |
# The whole point of this job: prove the validation above never needed steamcmd at all.
if command -v steamcmd >/dev/null 2>&1; then
echo "::error::steamcmd is present on this runner — this job must never install or invoke it."
exit 1
fi
echo "OK: steamcmd was never installed; every check above ran through -WhatIf only."