Release Healer Man 0.1.3
This commit is contained in:
@@ -20,3 +20,5 @@ releases/
|
|||||||
/content/
|
/content/
|
||||||
dist-android/
|
dist-android/
|
||||||
.android-public/
|
.android-public/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|||||||
+21
-10
@@ -176,17 +176,28 @@ schema-compatible with Healer Man and must not be copied over the new one.
|
|||||||
|
|
||||||
## Update workflow
|
## Update workflow
|
||||||
|
|
||||||
For a code update that includes a new APK, choose the next version first. The
|
For a code update that includes a new APK, the visual workflow in
|
||||||
example below upgrades `0.1.0` to `0.1.1`; replace `2` if it is not the next
|
`RELEASE_RUNBOOK.md` is recommended. For a manual release, choose the next
|
||||||
unused Android `versionCode`. Then run these commands in **Windows PowerShell**
|
version first. The example below upgrades `0.1.2` to `0.1.3`; replace `1003` if
|
||||||
|
it is not greater than every prior Android `versionCode`. Then run these
|
||||||
|
commands in **Windows PowerShell**
|
||||||
on the development PC. Stop if any test or build fails:
|
on the development PC. Stop if any test or build fails:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
Set-Location F:\Projects\HealerMan
|
Set-Location F:\Projects\HealerMan
|
||||||
|
|
||||||
$VersionName = '0.1.1'
|
$VersionName = '0.1.3'
|
||||||
$VersionCode = 2
|
$VersionCode = 1003
|
||||||
npm version $VersionName --no-git-tag-version
|
npm version $VersionName --no-git-tag-version
|
||||||
|
$ReleaseVersion = [ordered]@{
|
||||||
|
versionName = $VersionName
|
||||||
|
versionCode = $VersionCode
|
||||||
|
} | ConvertTo-Json
|
||||||
|
[IO.File]::WriteAllText(
|
||||||
|
(Join-Path $PWD 'release-version.json'),
|
||||||
|
"$ReleaseVersion`n",
|
||||||
|
[Text.UTF8Encoding]::new($false)
|
||||||
|
)
|
||||||
|
|
||||||
npm run content:test
|
npm run content:test
|
||||||
npm run server:test
|
npm run server:test
|
||||||
@@ -298,17 +309,17 @@ Gitea release/tag, and upload both attachments with:
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts/publish-gitea-release.ps1 `
|
powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts/publish-gitea-release.ps1 `
|
||||||
-VersionName 0.1.1 `
|
-VersionName 0.1.3 `
|
||||||
-VersionCode 2 `
|
-VersionCode 1003 `
|
||||||
-ReleaseNotes 'Healer Man client update.'
|
-ReleaseNotes 'Healer Man client update.'
|
||||||
```
|
```
|
||||||
|
|
||||||
The result contains:
|
The result contains:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Tag: v0.1.1
|
Tag: v0.1.3
|
||||||
APK: healer-man-0.1.1-release.apk
|
APK: healer-man-0.1.3-release.apk
|
||||||
Checksum: healer-man-0.1.1-release.apk.sha256
|
Checksum: healer-man-0.1.3-release.apk.sha256
|
||||||
```
|
```
|
||||||
|
|
||||||
Obtainium should monitor:
|
Obtainium should monitor:
|
||||||
|
|||||||
+54
-4
@@ -3,7 +3,42 @@
|
|||||||
Use this checklist whenever game changes need to be pushed to Gitea, deployed
|
Use this checklist whenever game changes need to be pushed to Gitea, deployed
|
||||||
to the TrueNAS server, and released as an Obtainium-compatible APK.
|
to the TrueNAS server, and released as an Obtainium-compatible APK.
|
||||||
|
|
||||||
The examples below use Android version `0.1.1` and `versionCode` `2`. Replace
|
## Visual release manager
|
||||||
|
|
||||||
|
On the development PC, double-click `scripts\release-manager.cmd` or run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
npm run release:gui
|
||||||
|
```
|
||||||
|
|
||||||
|
The release manager suggests the next semantic patch version and a strictly
|
||||||
|
increasing Android `versionCode`, runs the complete validation suite, and
|
||||||
|
stages the release. It then shows the exact staged paths and diff summary for
|
||||||
|
approval before it commits, pushes `main`, builds the signed APK, and publishes
|
||||||
|
the Gitea release. Signing passwords and the Gitea token are passed to the
|
||||||
|
child process in memory rather than on its command line.
|
||||||
|
|
||||||
|
The local release key is `F:\secure\healer-man-release.jks`. When the companion
|
||||||
|
`healer-man-release-password.dpapi` file is present, blank signing-password
|
||||||
|
fields are filled from that Windows user-encrypted credential in memory. The
|
||||||
|
encrypted credential is usable only by the Windows account that created it;
|
||||||
|
back up the keystore and preserve its password separately for disaster recovery.
|
||||||
|
The **Remember Gitea token on this PC** option stores the token the same way at
|
||||||
|
`F:\secure\healer-man-gitea-token.dpapi`, loads it on later launches, and keeps
|
||||||
|
it outside the repository. Use **Forget saved token** to remove it.
|
||||||
|
|
||||||
|
After the push, the GUI's **Deploy** tab contains the backup and fast-forward
|
||||||
|
pull block for the TrueNAS shell, pinned to the new commit. Stop the app in the
|
||||||
|
TrueNAS Apps UI before running it. Start the app afterward, watch its startup
|
||||||
|
log, and use the **Verify** tab for the health checks.
|
||||||
|
|
||||||
|
The canonical Android version is stored in `release-version.json`. The initial
|
||||||
|
`1002` code is intentionally above the historical codes in this runbook; the
|
||||||
|
GUI will suggest `1003` for version `0.1.3`. If APK publication fails after the
|
||||||
|
Git push, fix the release-side problem and use **Publish Current APK Only** so
|
||||||
|
the source version is not bumped a second time.
|
||||||
|
|
||||||
|
The examples below use Android version `0.1.3` and `versionCode` `1003`. Replace
|
||||||
both with the next values for the release you are making. Every APK must use a
|
both with the next values for the release you are making. Every APK must use a
|
||||||
larger `versionCode` than every APK previously released.
|
larger `versionCode` than every APK previously released.
|
||||||
|
|
||||||
@@ -150,8 +185,8 @@ other secrets.
|
|||||||
Choose both values before building:
|
Choose both values before building:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
$VersionName = '0.1.1'
|
$VersionName = '0.1.3'
|
||||||
$VersionCode = 2
|
$VersionCode = 1003
|
||||||
```
|
```
|
||||||
|
|
||||||
- `VersionName` is the player-facing semantic version.
|
- `VersionName` is the player-facing semantic version.
|
||||||
@@ -159,10 +194,20 @@ $VersionCode = 2
|
|||||||
- Record the last used `versionCode` in the Gitea release notes or your release
|
- Record the last used `versionCode` in the Gitea release notes or your release
|
||||||
records so it is never accidentally reused.
|
records so it is never accidentally reused.
|
||||||
|
|
||||||
Update `package.json` and `package-lock.json` to the same version:
|
Update `package.json`, `package-lock.json`, and the canonical Android release
|
||||||
|
metadata to the same version:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
npm version $VersionName --no-git-tag-version
|
npm version $VersionName --no-git-tag-version
|
||||||
|
$ReleaseVersion = [ordered]@{
|
||||||
|
versionName = $VersionName
|
||||||
|
versionCode = $VersionCode
|
||||||
|
} | ConvertTo-Json
|
||||||
|
[IO.File]::WriteAllText(
|
||||||
|
(Join-Path $PWD 'release-version.json'),
|
||||||
|
"$ReleaseVersion`n",
|
||||||
|
[Text.UTF8Encoding]::new($false)
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
Do not create a local Git tag with `npm version`; the release script creates the
|
Do not create a local Git tag with `npm version`; the release script creates the
|
||||||
@@ -544,6 +589,11 @@ An already-installed Android APK cannot be rolled back in place to a lower
|
|||||||
|
|
||||||
## Common failures
|
## Common failures
|
||||||
|
|
||||||
|
- **Android Vite build reports `JavaScript heap out of memory`:** the checked-in
|
||||||
|
`build:android:web` command gives Vite an 8 GB Node heap. Make sure the current
|
||||||
|
`package.json` contains that command and close other memory-heavy applications
|
||||||
|
before retrying. A failed GUI preparation restores the version files when no
|
||||||
|
release commit was created, so retry the same suggested version.
|
||||||
- **TrueNAS says the bare repository is not a Git repository:** run the pull
|
- **TrueNAS says the bare repository is not a Git repository:** run the pull
|
||||||
with `sudo`. The normal `truenas_admin` account cannot read Gitea's repository
|
with `sudo`. The normal `truenas_admin` account cannot read Gitea's repository
|
||||||
storage.
|
storage.
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ import groovy.json.JsonSlurper
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
def packageJson = new JsonSlurper().parse(file('../../package.json'))
|
def packageJson = new JsonSlurper().parse(file('../../package.json'))
|
||||||
|
def releaseVersionFile = file('../../release-version.json')
|
||||||
|
def releaseVersion = releaseVersionFile.exists() ? new JsonSlurper().parse(releaseVersionFile) : [:]
|
||||||
def releaseVersionName = System.getenv('ANDROID_VERSION_NAME') ?: packageJson.version
|
def releaseVersionName = System.getenv('ANDROID_VERSION_NAME') ?: packageJson.version
|
||||||
def releaseVersionCode = (System.getenv('ANDROID_VERSION_CODE') ?: '1').toInteger()
|
def releaseVersionCode = (System.getenv('ANDROID_VERSION_CODE') ?: releaseVersion.versionCode ?: '1').toInteger()
|
||||||
def releaseKeystoreFile = System.getenv('ANDROID_KEYSTORE_FILE')
|
def releaseKeystoreFile = System.getenv('ANDROID_KEYSTORE_FILE')
|
||||||
def releaseKeystorePassword = System.getenv('ANDROID_KEYSTORE_PASSWORD')
|
def releaseKeystorePassword = System.getenv('ANDROID_KEYSTORE_PASSWORD')
|
||||||
def releaseKeyAlias = System.getenv('ANDROID_KEY_ALIAS')
|
def releaseKeyAlias = System.getenv('ANDROID_KEY_ALIAS')
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "healer-man",
|
"name": "healer-man",
|
||||||
"version": "0.1.2",
|
"version": "0.1.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "healer-man",
|
"name": "healer-man",
|
||||||
"version": "0.1.2",
|
"version": "0.1.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor/android": "8.4.1",
|
"@capacitor/android": "8.4.1",
|
||||||
"@capacitor/core": "8.4.1",
|
"@capacitor/core": "8.4.1",
|
||||||
|
|||||||
+3
-2
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "healer-man",
|
"name": "healer-man",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.2",
|
"version": "0.1.3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host 0.0.0.0",
|
"dev": "vite --host 0.0.0.0",
|
||||||
@@ -90,11 +90,12 @@
|
|||||||
"assets:ktx2:refresh-metadata": "node scripts/asset-pipeline/refresh-ktx2-metadata.mjs",
|
"assets:ktx2:refresh-metadata": "node scripts/asset-pipeline/refresh-ktx2-metadata.mjs",
|
||||||
"prebuild": "npm run loot:generate && npm run assets:ktx2:audit",
|
"prebuild": "npm run loot:generate && npm run assets:ktx2:audit",
|
||||||
"build": "tsc -b && vite build",
|
"build": "tsc -b && vite build",
|
||||||
"build:android:web": "npm run prebuild && npm run android:prepare && tsc -b && vite build --mode android",
|
"build:android:web": "npm run prebuild && npm run android:prepare && tsc -b && node --max-old-space-size=8192 node_modules/vite/bin/vite.js build --mode android",
|
||||||
"android:sync": "npm run build:android:web && cap sync android",
|
"android:sync": "npm run build:android:web && cap sync android",
|
||||||
"android:apk": "npm run android:sync && powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts/runAndroidGradle.ps1 assembleDebug",
|
"android:apk": "npm run android:sync && powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts/runAndroidGradle.ps1 assembleDebug",
|
||||||
"android:apk:release": "npm run android:sync && powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts/runAndroidGradle.ps1 assembleRelease",
|
"android:apk:release": "npm run android:sync && powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts/runAndroidGradle.ps1 assembleRelease",
|
||||||
"android:install": "npm run android:sync && powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts/runAndroidGradle.ps1 installDebug",
|
"android:install": "npm run android:sync && powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts/runAndroidGradle.ps1 installDebug",
|
||||||
|
"release:gui": "python scripts/release_manager.py",
|
||||||
"preview": "vite preview --host 0.0.0.0",
|
"preview": "vite preview --host 0.0.0.0",
|
||||||
"pretest": "npm run loot:generate",
|
"pretest": "npm run loot:generate",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"versionName": "0.1.3",
|
||||||
|
"versionCode": 1003
|
||||||
|
}
|
||||||
@@ -28,13 +28,34 @@ foreach ($secret in $requiredSecrets) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$projectRoot = Split-Path -Parent $PSScriptRoot
|
$projectRoot = Split-Path -Parent $PSScriptRoot
|
||||||
|
$packageVersion = (Get-Content -LiteralPath (Join-Path $projectRoot 'package.json') -Raw | ConvertFrom-Json).version
|
||||||
|
$releaseVersionPath = Join-Path $projectRoot 'release-version.json'
|
||||||
|
if (-not (Test-Path -LiteralPath $releaseVersionPath)) {
|
||||||
|
throw "Canonical release metadata is missing: $releaseVersionPath"
|
||||||
|
}
|
||||||
|
$releaseVersion = Get-Content -LiteralPath $releaseVersionPath -Raw | ConvertFrom-Json
|
||||||
|
if ($packageVersion -ne $VersionName -or $releaseVersion.versionName -ne $VersionName) {
|
||||||
|
throw "VersionName $VersionName does not match package.json and release-version.json."
|
||||||
|
}
|
||||||
|
if ([int]$releaseVersion.versionCode -ne $VersionCode) {
|
||||||
|
throw "VersionCode $VersionCode does not match release-version.json ($($releaseVersion.versionCode))."
|
||||||
|
}
|
||||||
$env:ANDROID_VERSION_NAME = $VersionName
|
$env:ANDROID_VERSION_NAME = $VersionName
|
||||||
$env:ANDROID_VERSION_CODE = [string]$VersionCode
|
$env:ANDROID_VERSION_CODE = [string]$VersionCode
|
||||||
|
|
||||||
Push-Location $projectRoot
|
Push-Location $projectRoot
|
||||||
try {
|
try {
|
||||||
|
$initialGitStatus = & git status --porcelain
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "Unable to inspect the Git working tree." }
|
||||||
|
if ($initialGitStatus) { throw "APK publication requires a clean Git working tree." }
|
||||||
|
|
||||||
& npm run android:sync
|
& npm run android:sync
|
||||||
if ($LASTEXITCODE -ne 0) { throw "Android web build and Capacitor sync failed with exit code $LASTEXITCODE" }
|
if ($LASTEXITCODE -ne 0) { throw "Android web build and Capacitor sync failed with exit code $LASTEXITCODE" }
|
||||||
|
$postSyncGitStatus = & git status --porcelain --untracked-files=no
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "Unable to inspect Android sync changes." }
|
||||||
|
if ($postSyncGitStatus) {
|
||||||
|
throw "Android sync changed tracked files. Review and commit them before publishing the APK."
|
||||||
|
}
|
||||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts/runAndroidGradle.ps1 assembleRelease
|
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts/runAndroidGradle.ps1 assembleRelease
|
||||||
if ($LASTEXITCODE -ne 0) { throw "Android release build failed with exit code $LASTEXITCODE" }
|
if ($LASTEXITCODE -ne 0) { throw "Android release build failed with exit code $LASTEXITCODE" }
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
@echo off
|
||||||
|
setlocal
|
||||||
|
cd /d "%~dp0.."
|
||||||
|
where py >nul 2>&1
|
||||||
|
if errorlevel 1 goto use_python
|
||||||
|
py -3 scripts\release_manager.py %*
|
||||||
|
exit /b %errorlevel%
|
||||||
|
|
||||||
|
:use_python
|
||||||
|
python scripts\release_manager.py %*
|
||||||
|
exit /b %errorlevel%
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,49 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
import release_manager
|
||||||
|
|
||||||
|
|
||||||
|
class VersionTests(unittest.TestCase):
|
||||||
|
def test_bumps_each_semver_component(self):
|
||||||
|
self.assertEqual(release_manager.bump_semver("0.1.2", "patch"), "0.1.3")
|
||||||
|
self.assertEqual(release_manager.bump_semver("0.1.2", "minor"), "0.2.0")
|
||||||
|
self.assertEqual(release_manager.bump_semver("0.1.2", "major"), "1.0.0")
|
||||||
|
|
||||||
|
def test_android_code_is_monotonic(self):
|
||||||
|
self.assertEqual(release_manager.semantic_version_code("0.1.3"), 1003)
|
||||||
|
self.assertEqual(release_manager.suggest_version_code("0.1.3", 5000), 5001)
|
||||||
|
|
||||||
|
def test_rejects_non_release_semver(self):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
release_manager.parse_semver("1.2")
|
||||||
|
|
||||||
|
|
||||||
|
class SafetyTests(unittest.TestCase):
|
||||||
|
def test_detects_outputs_and_secrets(self):
|
||||||
|
paths = [
|
||||||
|
"src/game/example.ts",
|
||||||
|
"dist-android/assets/app.js",
|
||||||
|
"android/app/release.apk",
|
||||||
|
".env.production",
|
||||||
|
"FINE.key.ts",
|
||||||
|
"scripts/content/publish-content.mjs",
|
||||||
|
"public/data/healer-man-loot.sqlite",
|
||||||
|
]
|
||||||
|
self.assertEqual(
|
||||||
|
release_manager.find_dangerous_staged_paths(paths),
|
||||||
|
[
|
||||||
|
"dist-android/assets/app.js",
|
||||||
|
"android/app/release.apk",
|
||||||
|
".env.production",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_truenas_commands_pin_expected_commit(self):
|
||||||
|
block = release_manager.generate_truenas_deploy_commands("abc123")
|
||||||
|
self.assertIn("EXPECTED_COMMIT='abc123'", block)
|
||||||
|
self.assertIn("pull --ff-only", block)
|
||||||
|
self.assertIn('test "$ACTUAL_COMMIT" = "$EXPECTED_COMMIT"', block)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -8,7 +8,8 @@ $ErrorActionPreference = 'Stop'
|
|||||||
$projectRoot = Split-Path -Parent $PSScriptRoot
|
$projectRoot = Split-Path -Parent $PSScriptRoot
|
||||||
$documentsRoot = Split-Path -Parent $projectRoot
|
$documentsRoot = Split-Path -Parent $projectRoot
|
||||||
$wowRoot = Join-Path $documentsRoot 'wow335a'
|
$wowRoot = Join-Path $documentsRoot 'wow335a'
|
||||||
$jdkRoot = Join-Path $wowRoot 'dungeon-export-work\tools\temurin-21'
|
$projectJdkRoot = Join-Path $projectRoot '.tools\temurin-21'
|
||||||
|
$legacyJdkRoot = Join-Path $wowRoot 'dungeon-export-work\tools\temurin-21'
|
||||||
$portableSdkRoot = Join-Path $wowRoot '.android-sdk'
|
$portableSdkRoot = Join-Path $wowRoot '.android-sdk'
|
||||||
|
|
||||||
$jdkHome = $null
|
$jdkHome = $null
|
||||||
@@ -16,14 +17,17 @@ $configuredJavaHome = [Environment]::GetEnvironmentVariable('JAVA_HOME')
|
|||||||
if ($configuredJavaHome -and (Test-Path -LiteralPath (Join-Path $configuredJavaHome 'bin\java.exe'))) {
|
if ($configuredJavaHome -and (Test-Path -LiteralPath (Join-Path $configuredJavaHome 'bin\java.exe'))) {
|
||||||
$jdkHome = Get-Item -LiteralPath $configuredJavaHome
|
$jdkHome = Get-Item -LiteralPath $configuredJavaHome
|
||||||
} else {
|
} else {
|
||||||
$jdkHome = Get-ChildItem -LiteralPath $jdkRoot -Directory -ErrorAction SilentlyContinue |
|
$jdkHome = @($projectJdkRoot, $legacyJdkRoot) |
|
||||||
Where-Object { Test-Path -LiteralPath (Join-Path $_.FullName 'bin\java.exe') } |
|
ForEach-Object {
|
||||||
|
Get-ChildItem -LiteralPath $_ -Directory -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { Test-Path -LiteralPath (Join-Path $_.FullName 'bin\java.exe') }
|
||||||
|
} |
|
||||||
Sort-Object Name -Descending |
|
Sort-Object Name -Descending |
|
||||||
Select-Object -First 1
|
Select-Object -First 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if (-not $jdkHome) {
|
if (-not $jdkHome) {
|
||||||
throw "Java 21 was not found. Set JAVA_HOME or install the portable JDK under $jdkRoot"
|
throw "Java 21 was not found. Set JAVA_HOME or install the portable JDK under $projectJdkRoot"
|
||||||
}
|
}
|
||||||
|
|
||||||
$configuredSdkRoot = [Environment]::GetEnvironmentVariable('ANDROID_SDK_ROOT')
|
$configuredSdkRoot = [Environment]::GetEnvironmentVariable('ANDROID_SDK_ROOT')
|
||||||
|
|||||||
Reference in New Issue
Block a user