Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .githooks/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/bin/sh
# Blocks a push that would publish a credential.
#
# This runs here rather than in CI on purpose. A secret-scanning job on GitHub Actions only fires
# after the objects are already on GitHub, where they stay reachable through the API even if the
# branch is deleted, so a red build reports the leak instead of preventing it. CI runs the checks
# that are still useful once the code is public: the dependency audit, the type check, the tests.
#
# Enable in a fresh clone with: git config core.hooksPath .githooks
# Bypass deliberately with: git push --no-verify
set -e

root=$(git rev-parse --show-toplevel)

if ! command -v node >/dev/null 2>&1; then
echo "pre-push: node is not on PATH, so the secret scan cannot run." >&2
echo "pre-push: install Node or push with --no-verify if you have checked the diff yourself." >&2
exit 1
fi

node "$root/backend/scripts/secret-scan.mjs"
14 changes: 7 additions & 7 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ body:
attributes:
label: SS2 Revive version
description: >-
The first SS2 Revive line in the log says it: `SS2 Revive 0.2.0 starting.`
placeholder: "0.2.0"
The first SS2 Revive line in the log says it: `SS2 Revive 1.2.0 starting.`
placeholder: "1.2.0"
validations:
required: true

Expand All @@ -42,10 +42,10 @@ body:
attributes:
label: Game build
description: >-
The line straight after that one, at the end: `Unity ... | product ... | version '1.3.1.276'`.
Anything other than 1.3.1.276 is untested, which is worth knowing rather than a reason not
to report.
placeholder: "1.3.1.276"
The line straight after that one, at the end: `Unity ... | product ... | version '1.3.7.3054'`.
SS2 Revive supports build 1.3.7.3054. Version 1.5.x removed the networking code the mod
restores and cannot work.
placeholder: "1.3.7.3054"
validations:
required: true

Expand All @@ -65,7 +65,7 @@ body:
placeholder: |
1. Launched the game
2. Opened the party menu
3. Invited a friend with F10
3. Invited a friend through Steam
4. ...
validations:
required: true
Expand Down
136 changes: 136 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
name: CI

# What this deliberately does not do is scan for committed secrets. That check has to happen before
# the objects reach GitHub, or it is only a notification that the leak already shipped, so it lives
# in .githooks/pre-push instead. Everything here is a check that is still worth running on code that
# is already public.

on:
push:
branches: ["**"]
# Tagged pushes are the release build's job; running both would duplicate the work.
tags-ignore: ["v*.*.*"]
pull_request:
# Advisories are published against dependencies that have not changed, so the audit needs a clock
# of its own rather than waiting for the next commit.
schedule:
- cron: "23 6 * * 1"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

env:
WRANGLER_SEND_METRICS: "false"

jobs:
worker:
name: Worker types and tests
runs-on: ubuntu-latest
timeout-minutes: 15
defaults:
run:
working-directory: backend

steps:
- name: Check out source
uses: actions/checkout@v6
with:
persist-credentials: false

- name: Set up pnpm
uses: pnpm/action-setup@v4
with:
# There is no package.json at the repository root, so the version has to be pointed at the
# backend workspace explicitly instead of being discovered.
package_json_file: backend/package.json

- name: Set up Node
uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
cache-dependency-path: backend/pnpm-lock.yaml

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Check generated bindings and types
# Regenerates worker-configuration.d.ts from wrangler.jsonc and fails if the committed copy
# has drifted, then type-checks against it. A binding added to the config but never wired up
# is caught here rather than at deploy time.
run: pnpm run check

- name: Run Worker tests
# Real Miniflare D1 and R2, including the migrations, so a migration that does not apply
# cleanly fails here instead of against production.
run: pnpm test

- name: Build Worker bundle
# A dry-run deploy. Proves the Worker still bundles without needing any Cloudflare
# credentials in CI.
run: pnpm run build

audit:
name: Dependency audit
runs-on: ubuntu-latest
timeout-minutes: 10
defaults:
run:
working-directory: backend

steps:
- name: Check out source
uses: actions/checkout@v6
with:
persist-credentials: false

- name: Set up pnpm
uses: pnpm/action-setup@v4
with:
package_json_file: backend/package.json

- name: Set up Node
uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
cache-dependency-path: backend/pnpm-lock.yaml

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Audit dependencies
# Kept in its own job so a newly published advisory reports as an advisory, rather than
# turning up as a mystery failure in the middle of the test job.
run: pnpm run security:audit

data:
name: SS2Revive_Data tests
runs-on: windows-latest
timeout-minutes: 15

steps:
- name: Check out source
uses: actions/checkout@v6
with:
persist-credentials: false

- name: Set up .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.0.x

- name: Run data tests
# SS2Revive_Data references no game assembly, and the runner reports SKIP for the checks
# that need an installed copy of the game, so this is meaningful on a bare runner. The
# plugin itself is not built here: SS2Revive.csproj compiles against the game's Managed
# directory, which no hosted runner has.
shell: pwsh
run: |
dotnet run --project tests/DataTests/DataTests.csproj --configuration Release
if ($LASTEXITCODE -ne 0) { throw 'Data tests failed.' }
144 changes: 144 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
name: Build draft release

on:
push:
tags:
- "v*.*.*"

permissions:
contents: write

concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false

jobs:
setup:
name: Build Windows setup
runs-on: windows-latest
timeout-minutes: 20

steps:
- name: Check out tagged source
uses: actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: false

- name: Set up .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.0.x

- name: Validate tag and project version
id: metadata
shell: pwsh
env:
RELEASE_TAG_INPUT: ${{ github.ref_name }}
run: |
$tag = $env:RELEASE_TAG_INPUT
if ($tag -notmatch '^v(?<version>\d+\.\d+\.\d+)$') {
throw "Release tags must use the exact vMAJOR.MINOR.PATCH form. Received: $tag"
}

[xml]$props = Get-Content -LiteralPath 'Directory.Build.props'
$projectVersion = [string]$props.Project.PropertyGroup.SS2ReviveVersion
$version = $Matches.version
if ($projectVersion -ne $version) {
throw "Tag $tag does not match SS2ReviveVersion $projectVersion."
}

"tag=$tag" >> $env:GITHUB_OUTPUT
"version=$version" >> $env:GITHUB_OUTPUT

- name: Publish single-file setup
shell: pwsh
run: |
$output = Join-Path $env:RUNNER_TEMP 'ss2revive-release'
dotnet publish tools/Setup/SS2Revive.Setup.csproj `
--configuration Release `
--runtime win-x64 `
--self-contained true `
--output $output
if ($LASTEXITCODE -ne 0) { throw 'Setup publish failed.' }

$source = Join-Path $output 'SS2Revive.Setup.exe'
$name = 'SS2Revive-Setup-${{ steps.metadata.outputs.version }}.exe'
$destination = Join-Path $output $name
Move-Item -LiteralPath $source -Destination $destination

& $destination --self-test
if ($LASTEXITCODE -ne 0) { throw 'Published setup self-test failed.' }

$hash = (Get-FileHash -LiteralPath $destination -Algorithm SHA256).Hash.ToLowerInvariant()
"$hash $name" | Set-Content `
-LiteralPath (Join-Path $output "$name.sha256") `
-Encoding ascii `
-NoNewline

- name: Prepare draft release notes
shell: pwsh
run: |
@'
# SS2 Revive ${{ steps.metadata.outputs.tag }}

SS2 Revive restores multiplayer, progression, Creation Mode, and community-made maps for Surgeon Simulator 2 build 1.3.7.3054.

## Install

1. Download `SS2Revive-Setup-${{ steps.metadata.outputs.version }}.exe`.
2. Verify it with the attached `.sha256` file.
3. Run Setup, choose an install location, and enter the Steam login name for an account that owns Surgeon Simulator 2.
4. Complete the password and Steam Guard prompts in DepotDownloader's separate window.
5. Start the game with the launcher Setup creates.

The setup executable is currently unsigned, so Windows may display a SmartScreen warning.

## Maintainer checklist before publishing

- Attach `SS2Revive-${{ steps.metadata.outputs.version }}.zip`, created locally with `./pack.ps1`.
- Confirm the mod ZIP contains `SS2Revive.dll`, `SS2Revive_Data.dll`, and `newsfeed/NewsFeed.json`.
- Confirm the public community service is healthy.
- Replace or expand these draft notes from the local ignored release-description document if needed.
- Publish this draft only after all assets are present.

## Compatibility

Windows x64 and Surgeon Simulator 2 build 1.3.7.3054 are required. Legacy maps are preserved as-is and some may contain bugs, fail to load, or open to a black screen.
'@ | Set-Content -LiteralPath (Join-Path $env:RUNNER_TEMP 'release-notes.md') -Encoding utf8

- name: Create or update draft release
shell: pwsh
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ steps.metadata.outputs.tag }}
RELEASE_VERSION: ${{ steps.metadata.outputs.version }}
run: |
$output = Join-Path $env:RUNNER_TEMP 'ss2revive-release'
$exe = Join-Path $output "SS2Revive-Setup-$env:RELEASE_VERSION.exe"
$checksum = "$exe.sha256"
$notes = Join-Path $env:RUNNER_TEMP 'release-notes.md'

$existing = & gh release view $env:RELEASE_TAG --json isDraft 2>$null
if ($LASTEXITCODE -eq 0) {
$release = $existing | ConvertFrom-Json
if (-not $release.isDraft) {
throw "Release $env:RELEASE_TAG is already public; refusing to replace its assets."
}

gh release upload $env:RELEASE_TAG $exe $checksum --clobber
if ($LASTEXITCODE -ne 0) { throw 'Uploading setup assets failed.' }
gh release edit $env:RELEASE_TAG `
--draft `
--title "SS2 Revive $env:RELEASE_TAG" `
--notes-file $notes
if ($LASTEXITCODE -ne 0) { throw 'Updating the draft release failed.' }
}
else {
gh release create $env:RELEASE_TAG $exe $checksum `
--verify-tag `
--draft `
--title "SS2 Revive $env:RELEASE_TAG" `
--notes-file $notes
if ($LASTEXITCODE -ne 0) { throw 'Creating the draft release failed.' }
}
15 changes: 14 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ obj/
# Local machine paths live here and must never be published.
Directory.Build.user.props

# Working notes and protocol write-ups. Kept on disk, not in the repository.
# Documentation is retained locally and transferred manually, not published from this branch.
docs/
*.md

# BepInEx is linked from the README, never vendored here.
lib/
Expand All @@ -25,3 +26,15 @@ dist/
# Inventory.dat, ProgressionConfig.json and the news tile artwork.
*.dat
assets/newsfeed/images/*.png

# Local Cloudflare Worker development. Never commit local state or secrets.
backend/node_modules/
backend/**/node_modules/
backend/**/.wrangler/
backend/**/.dev.vars
backend/**/.env*
backend/**/wrangler.production.jsonc
backend/**/coverage/
backend/**/dist/
backend/**/dist-production/
.pnpm-store/
Loading
Loading