feat: customize launcher version check and CI/CD release workflow for G-Launcher
This commit is contained in:
1 parent
a5205d1bed
commit
88a1f1c40a
12 files changed
+420
-46
No files matched your search
@@ -0,0 +1,133 @@
|
|||||||
|
name: Build G-Launcher Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag_name:
|
||||||
|
description: 'Release Tag Name (e.g. latest, release, v1.0.0)'
|
||||||
|
required: false
|
||||||
|
default: 'release'
|
||||||
|
custom_version:
|
||||||
|
description: 'Override Version (leave blank for auto-increment)'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: build-g-launcher-release
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: windows-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
cache-dependency-path: go.sum
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
cache: npm
|
||||||
|
cache-dependency-path: frontend/package-lock.json
|
||||||
|
|
||||||
|
- name: Install Wails CLI
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
go install github.com/wailsapp/wails/v3/cmd/wails3@v3.0.0-alpha.74
|
||||||
|
"$env:USERPROFILE\go\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||||
|
|
||||||
|
- name: Calculate launcher version
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$customVersion = "${{ github.event.inputs.custom_version }}"
|
||||||
|
$releaseTag = if ("${{ github.event.inputs.tag_name }}") { "${{ github.event.inputs.tag_name }}" } else { "release" }
|
||||||
|
|
||||||
|
if ($customVersion -and $customVersion.Trim() -ne "") {
|
||||||
|
$version = $customVersion.Trim()
|
||||||
|
Write-Host "Using user-specified version: $version"
|
||||||
|
} else {
|
||||||
|
$manifestPath = Join-Path $env:RUNNER_TEMP "latest.json"
|
||||||
|
# Attempt to fetch latest.json from current repository release
|
||||||
|
$repo = "${{ github.repository }}"
|
||||||
|
$manifestUrl = "https://github.com/$repo/releases/download/$releaseTag/latest.json"
|
||||||
|
$cacheBust = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
|
||||||
|
$requestUrl = "$($manifestUrl)?cachebust=$cacheBust"
|
||||||
|
|
||||||
|
Write-Host "Checking current manifest from: $requestUrl"
|
||||||
|
$status = & curl.exe --location --silent --show-error --output $manifestPath --write-out "%{http_code}" $requestUrl
|
||||||
|
|
||||||
|
if ($status -eq "200") {
|
||||||
|
$currentVersion = (Get-Content -Raw $manifestPath | ConvertFrom-Json).version
|
||||||
|
Write-Host "Found previous version: $currentVersion"
|
||||||
|
$version = node scripts/launcher-release.mjs next $currentVersion
|
||||||
|
} else {
|
||||||
|
Write-Host "No previous manifest found (HTTP $status). Starting at 1.0.0"
|
||||||
|
$version = "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[System.IO.File]::AppendAllText($env:GITHUB_ENV, "LAUNCHER_VERSION=$version" + [Environment]::NewLine)
|
||||||
|
[System.IO.File]::AppendAllText($env:GITHUB_ENV, "RELEASE_TAG=$releaseTag" + [Environment]::NewLine)
|
||||||
|
|
||||||
|
node scripts/launcher-release.mjs stamp-info $version build/windows/info.json
|
||||||
|
Write-Host "Configured G-Launcher version $version"
|
||||||
|
|
||||||
|
- name: Install Frontend Dependencies
|
||||||
|
working-directory: frontend
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build Frontend
|
||||||
|
working-directory: frontend
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Build Windows Executable
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
wails3 task build BUILD_VERSION=$env:LAUNCHER_VERSION PRODUCTION=true
|
||||||
|
env:
|
||||||
|
PRODUCTION: "true"
|
||||||
|
|
||||||
|
- name: Prepare Release Assets
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$builtLauncher = "bin/G-Launcher.exe"
|
||||||
|
if (!(Test-Path $builtLauncher)) {
|
||||||
|
$fallback = Get-ChildItem -Path "bin" -Filter "*.exe" | Select-Object -First 1
|
||||||
|
if ($fallback) {
|
||||||
|
$builtLauncher = $fallback.FullName
|
||||||
|
} else {
|
||||||
|
throw "Build output was not found in bin directory."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$releaseLauncher = Join-Path $env:RUNNER_TEMP "G-Launcher.exe"
|
||||||
|
Copy-Item -LiteralPath $builtLauncher -Destination $releaseLauncher -Force
|
||||||
|
|
||||||
|
$manifestPath = Join-Path $env:RUNNER_TEMP "latest.json"
|
||||||
|
node scripts/launcher-release.mjs manifest $env:LAUNCHER_VERSION $releaseLauncher $manifestPath
|
||||||
|
|
||||||
|
Write-Host "Release assets prepared:"
|
||||||
|
Get-Item $releaseLauncher, $manifestPath | Select-Object Name, Length
|
||||||
|
|
||||||
|
- name: Publish Release Assets
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
tag_name: ${{ env.RELEASE_TAG }}
|
||||||
|
files: |
|
||||||
|
${{ runner.temp }}/G-Launcher.exe
|
||||||
|
${{ runner.temp }}/latest.json
|
||||||
|
overwrite_files: true
|
||||||
|
fail_on_unmatched_files: true
|
||||||
@@ -34,6 +34,5 @@ release:
|
|||||||
|
|
||||||
update:
|
update:
|
||||||
@echo Updating build assets...
|
@echo Updating build assets...
|
||||||
wails3 update build-assets -name "firefly-launcher" -binaryname "firefly-launcher" -config build/config.yml
|
wails3 update build-assets -name "g-launcher" -binaryname "g-launcher" -config build/config.yml
|
||||||
@echo Done!
|
@echo Done!
|
||||||
|
|
||||||
+1
-2
@@ -7,7 +7,7 @@ includes:
|
|||||||
linux: ./build/linux/Taskfile.yml
|
linux: ./build/linux/Taskfile.yml
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
APP_NAME: "firefly-launcher"
|
APP_NAME: "G-Launcher"
|
||||||
BIN_DIR: "bin"
|
BIN_DIR: "bin"
|
||||||
VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}'
|
VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}'
|
||||||
|
|
||||||
@@ -31,4 +31,3 @@ tasks:
|
|||||||
summary: Runs the application in development mode
|
summary: Runs the application in development mode
|
||||||
cmds:
|
cmds:
|
||||||
- wails3 dev -config ./build/config.yml -port {{.VITE_PORT}}
|
- wails3 dev -config ./build/config.yml -port {{.VITE_PORT}}
|
||||||
|
|
||||||
@@ -5,11 +5,11 @@
|
|||||||
"info": {
|
"info": {
|
||||||
"0000": {
|
"0000": {
|
||||||
"ProductVersion": "1.0.0",
|
"ProductVersion": "1.0.0",
|
||||||
"CompanyName": "Firefly Shelter",
|
"CompanyName": "GaCy",
|
||||||
"FileDescription": "Firefly Launcher",
|
"FileDescription": "G-Launcher",
|
||||||
"LegalCopyright": "@ 2025, Firefly Shelter",
|
"LegalCopyright": "@ 2026, GaCy",
|
||||||
"ProductName": "Firefly Launcher",
|
"ProductName": "G-Launcher",
|
||||||
"Comments": "Firefly Launcher"
|
"Comments": "G-Launcher"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,19 +5,19 @@
|
|||||||
!include "FileFunc.nsh"
|
!include "FileFunc.nsh"
|
||||||
|
|
||||||
!ifndef INFO_PROJECTNAME
|
!ifndef INFO_PROJECTNAME
|
||||||
!define INFO_PROJECTNAME "firefly-launcher"
|
!define INFO_PROJECTNAME "g-launcher"
|
||||||
!endif
|
!endif
|
||||||
!ifndef INFO_COMPANYNAME
|
!ifndef INFO_COMPANYNAME
|
||||||
!define INFO_COMPANYNAME "Firefly Shelter"
|
!define INFO_COMPANYNAME "GaCy"
|
||||||
!endif
|
!endif
|
||||||
!ifndef INFO_PRODUCTNAME
|
!ifndef INFO_PRODUCTNAME
|
||||||
!define INFO_PRODUCTNAME "Firefly Launcher"
|
!define INFO_PRODUCTNAME "G-Launcher"
|
||||||
!endif
|
!endif
|
||||||
!ifndef INFO_PRODUCTVERSION
|
!ifndef INFO_PRODUCTVERSION
|
||||||
!define INFO_PRODUCTVERSION "1.0.0"
|
!define INFO_PRODUCTVERSION "1.0.0"
|
||||||
!endif
|
!endif
|
||||||
!ifndef INFO_COPYRIGHT
|
!ifndef INFO_COPYRIGHT
|
||||||
!define INFO_COPYRIGHT "@ 2025, Firefly Shelter"
|
!define INFO_COPYRIGHT "@ 2025, GaCy"
|
||||||
!endif
|
!endif
|
||||||
!ifndef PRODUCT_EXECUTABLE
|
!ifndef PRODUCT_EXECUTABLE
|
||||||
!define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe"
|
!define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||||
<assemblyIdentity type="win32" name="com.fireflyshelter.fireflylauncher" version="1.0.0" processorArchitecture="*"/>
|
<assemblyIdentity type="win32" name="com.gacy.glauncher" version="1.0.0" processorArchitecture="*"/>
|
||||||
<dependency>
|
<dependency>
|
||||||
<dependentAssembly>
|
<dependentAssembly>
|
||||||
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||||
|
|||||||
@@ -1,48 +1,78 @@
|
|||||||
package gitService
|
package gitService
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/subtle"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"g-launcher/pkg/constant"
|
"fmt"
|
||||||
"g-launcher/pkg/models"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"github.com/minio/selfupdate"
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"g-launcher/pkg/constant"
|
||||||
|
"g-launcher/pkg/models"
|
||||||
|
|
||||||
|
"github.com/minio/selfupdate"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GitService struct{}
|
type GitService struct{}
|
||||||
|
|
||||||
|
type launcherManifest struct {
|
||||||
func (g *GitService) GetLatestLauncherVersion() (bool, string, string) {
|
Version string `json:"version"`
|
||||||
resp, err := http.Get(constant.LauncherGitUrl)
|
SHA256 string `json:"sha256"`
|
||||||
if err != nil {
|
|
||||||
return false, "", err.Error()
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
body, _ := io.ReadAll(resp.Body)
|
|
||||||
|
|
||||||
var releases []models.ReleaseType
|
|
||||||
err = json.Unmarshal(body, &releases)
|
|
||||||
if err != nil {
|
|
||||||
return false, "", err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(releases) == 0 {
|
|
||||||
return false, "", "no releases found"
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, releases[0].TagName, ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var launcherVersionPattern = regexp.MustCompile(`^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$`)
|
||||||
|
|
||||||
|
func (g *GitService) GetLatestLauncherVersion() (bool, string, string) {
|
||||||
func (g *GitService) UpdateLauncherProgress(version string) (bool, string) {
|
// First, attempt to check via latest.json manifest
|
||||||
asset, ok := g.getReleaseAsset(version, constant.LauncherGitUrl, constant.LauncherFile)
|
if constant.LauncherManifestURL != "" {
|
||||||
if !ok {
|
manifest, err := fetchLauncherManifest(constant.LauncherManifestURL)
|
||||||
return false, "no release found"
|
if err == nil && manifest.Version != "" {
|
||||||
|
return true, manifest.Version, ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fallback to Gitea/Forgejo/GitHub Releases API
|
||||||
|
if constant.LauncherGitUrl != "" {
|
||||||
|
resp, err := http.Get(constant.LauncherGitUrl)
|
||||||
|
if err == nil {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, readErr := io.ReadAll(resp.Body)
|
||||||
|
if readErr == nil {
|
||||||
|
var releases []models.ReleaseType
|
||||||
|
if json.Unmarshal(body, &releases) == nil && len(releases) > 0 {
|
||||||
|
return true, releases[0].TagName, ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, "", "no launcher updates found"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GitService) UpdateLauncherProgress(version string) (bool, string) {
|
||||||
|
if !constant.LauncherUpdatesEnabled {
|
||||||
|
return false, "launcher updates are disabled in development builds"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mode 1: Manifest-based update with SHA-256 integrity verification
|
||||||
|
if constant.LauncherManifestURL != "" && constant.LauncherDownloadURL != "" {
|
||||||
|
manifest, err := fetchLauncherManifest(constant.LauncherManifestURL)
|
||||||
|
if err == nil && manifest.Version == version && manifest.SHA256 != "" {
|
||||||
|
return g.applyManifestUpdate(manifest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mode 2: Release API fallback
|
||||||
|
if constant.LauncherGitUrl != "" {
|
||||||
|
asset, ok := g.getReleaseAsset(version, constant.LauncherGitUrl, constant.LauncherFile)
|
||||||
|
if ok && asset.BrowserDownloadURL != "" {
|
||||||
resp, err := http.Get(asset.BrowserDownloadURL)
|
resp, err := http.Get(asset.BrowserDownloadURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err.Error()
|
return false, err.Error()
|
||||||
@@ -54,4 +84,96 @@ func (g *GitService) UpdateLauncherProgress(version string) (bool, string) {
|
|||||||
return false, err.Error()
|
return false, err.Error()
|
||||||
}
|
}
|
||||||
return true, ""
|
return true, ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, "could not locate valid launcher update package"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GitService) applyManifestUpdate(manifest launcherManifest) (bool, string) {
|
||||||
|
client := &http.Client{Timeout: 10 * time.Minute}
|
||||||
|
response, err := client.Get(constant.LauncherDownloadURL)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("download launcher update: %w", err).Error()
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices {
|
||||||
|
return false, fmt.Sprintf("download launcher update: server returned %s", response.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
temporaryFile, err := os.CreateTemp("", "g-launcher-update-*.exe")
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("create temporary launcher update: %w", err).Error()
|
||||||
|
}
|
||||||
|
temporaryPath := temporaryFile.Name()
|
||||||
|
defer os.Remove(temporaryPath)
|
||||||
|
|
||||||
|
hash := sha256.New()
|
||||||
|
if _, err := io.Copy(io.MultiWriter(temporaryFile, hash), response.Body); err != nil {
|
||||||
|
temporaryFile.Close()
|
||||||
|
return false, fmt.Errorf("save launcher update: %w", err).Error()
|
||||||
|
}
|
||||||
|
if err := temporaryFile.Close(); err != nil {
|
||||||
|
return false, fmt.Errorf("close launcher update: %w", err).Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedHash, err := hex.DecodeString(manifest.SHA256)
|
||||||
|
if err != nil || subtle.ConstantTimeCompare(hash.Sum(nil), expectedHash) != 1 {
|
||||||
|
return false, "launcher update checksum mismatch"
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFile, err := os.Open(temporaryPath)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("open launcher update: %w", err).Error()
|
||||||
|
}
|
||||||
|
defer updateFile.Close()
|
||||||
|
if err := selfupdate.Apply(updateFile, selfupdate.Options{}); err != nil {
|
||||||
|
return false, fmt.Errorf("apply launcher update: %w", err).Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchLauncherManifest(manifestURL string) (launcherManifest, error) {
|
||||||
|
parsedURL, err := url.Parse(manifestURL)
|
||||||
|
if err != nil || (parsedURL.Scheme != "https" && parsedURL.Scheme != "http") {
|
||||||
|
return launcherManifest{}, fmt.Errorf("invalid launcher manifest URL")
|
||||||
|
}
|
||||||
|
query := parsedURL.Query()
|
||||||
|
query.Set("_", fmt.Sprintf("%d", time.Now().UnixNano()))
|
||||||
|
parsedURL.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
request, err := http.NewRequest(http.MethodGet, parsedURL.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return launcherManifest{}, fmt.Errorf("create launcher manifest request: %w", err)
|
||||||
|
}
|
||||||
|
request.Header.Set("Cache-Control", "no-cache")
|
||||||
|
request.Header.Set("User-Agent", "G-Launcher")
|
||||||
|
|
||||||
|
client := &http.Client{Timeout: 20 * time.Second}
|
||||||
|
response, err := client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return launcherManifest{}, fmt.Errorf("check launcher updates: %w", err)
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices {
|
||||||
|
return launcherManifest{}, fmt.Errorf("check launcher updates: server returned %s", response.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
var manifest launcherManifest
|
||||||
|
if err := json.NewDecoder(io.LimitReader(response.Body, 1<<20)).Decode(&manifest); err != nil {
|
||||||
|
return launcherManifest{}, fmt.Errorf("read launcher update manifest: %w", err)
|
||||||
|
}
|
||||||
|
if !launcherVersionPattern.MatchString(manifest.Version) {
|
||||||
|
return launcherManifest{}, fmt.Errorf("launcher update manifest has invalid version %q", manifest.Version)
|
||||||
|
}
|
||||||
|
manifest.SHA256 = strings.TrimSpace(manifest.SHA256)
|
||||||
|
if len(manifest.SHA256) != sha256.Size*2 {
|
||||||
|
return launcherManifest{}, fmt.Errorf("launcher update manifest has invalid SHA-256")
|
||||||
|
}
|
||||||
|
if _, err := hex.DecodeString(manifest.SHA256); err != nil {
|
||||||
|
return launcherManifest{}, fmt.Errorf("launcher update manifest has invalid SHA-256: %w", err)
|
||||||
|
}
|
||||||
|
manifest.SHA256 = strings.ToLower(manifest.SHA256)
|
||||||
|
return manifest, nil
|
||||||
}
|
}
|
||||||
@@ -2,15 +2,18 @@ package constant
|
|||||||
|
|
||||||
const ProxyGitUrl = "https://git.kain.io.vn/api/v1/repos/Firefly-Shelter/FireflyGo_Proxy/releases"
|
const ProxyGitUrl = "https://git.kain.io.vn/api/v1/repos/Firefly-Shelter/FireflyGo_Proxy/releases"
|
||||||
const ServerGitUrl = "https://git.kain.io.vn/api/v1/repos/Firefly-Shelter/FireflyGo_Local_Archive/releases"
|
const ServerGitUrl = "https://git.kain.io.vn/api/v1/repos/Firefly-Shelter/FireflyGo_Local_Archive/releases"
|
||||||
const LauncherGitUrl = "https://git.kain.io.vn/api/v1/repos/Firefly-Shelter/Firefly_Launcher/releases"
|
const LauncherGitUrl = "https://gaytea.cod-algieba.ts.net/api/v1/repos/GaCy/G-Launcher/releases"
|
||||||
|
const LauncherManifestURL = "https://gaytea.cod-algieba.ts.net/GaCy/G-Launcher/releases/download/latest/latest.json"
|
||||||
|
const LauncherDownloadURL = "https://gaytea.cod-algieba.ts.net/GaCy/G-Launcher/releases/download/latest/G-Launcher.exe"
|
||||||
|
|
||||||
const ServerStorageUrl = "./server"
|
const ServerStorageUrl = "./server"
|
||||||
const ProxyStorageUrl = "./proxy"
|
const ProxyStorageUrl = "./proxy"
|
||||||
const ServerZipFile = "prebuild_win_x86.zip"
|
const ServerZipFile = "prebuild_win_x86.zip"
|
||||||
const ProxyFile = "firefly-go-proxy.exe"
|
const ProxyFile = "firefly-go-proxy.exe"
|
||||||
const LauncherFile = "g-launcher.exe"
|
const LauncherFile = "G-Launcher.exe"
|
||||||
const TempUrl = "./temp"
|
const TempUrl = "./temp"
|
||||||
|
|
||||||
const CurrentLauncherVersion = "1.0.0"
|
var CurrentLauncherVersion = "1.0.0"
|
||||||
|
|
||||||
type ToolFile string
|
type ToolFile string
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
//go:build !production
|
||||||
|
|
||||||
|
package constant
|
||||||
|
|
||||||
|
const LauncherUpdatesEnabled = false
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
//go:build production
|
||||||
|
|
||||||
|
package constant
|
||||||
|
|
||||||
|
const LauncherUpdatesEnabled = true
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { createHash } from "node:crypto";
|
||||||
|
import { readFile, writeFile } from "node:fs/promises";
|
||||||
|
import { pathToFileURL } from "node:url";
|
||||||
|
|
||||||
|
const versionPattern = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
||||||
|
|
||||||
|
export function nextLauncherVersion(currentVersion) {
|
||||||
|
const match = currentVersion.match(versionPattern);
|
||||||
|
if (!match) {
|
||||||
|
throw new Error(`Invalid launcher version: ${currentVersion}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let [major, minor, patch] = match.slice(1).map(Number);
|
||||||
|
patch += 1;
|
||||||
|
if (patch === 10) {
|
||||||
|
patch = 0;
|
||||||
|
minor += 1;
|
||||||
|
if (minor === 10) {
|
||||||
|
minor = 0;
|
||||||
|
major += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${major}.${minor}.${patch}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createLauncherManifest(version, executablePath) {
|
||||||
|
if (!versionPattern.test(version)) {
|
||||||
|
throw new Error(`Invalid launcher version: ${version}`);
|
||||||
|
}
|
||||||
|
const executable = await readFile(executablePath);
|
||||||
|
return {
|
||||||
|
version,
|
||||||
|
sha256: createHash("sha256").update(executable).digest("hex"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main(args) {
|
||||||
|
const [command, ...values] = args;
|
||||||
|
if (command === "next" && values.length === 1) {
|
||||||
|
console.log(nextLauncherVersion(values[0]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command === "manifest" && values.length === 3) {
|
||||||
|
const [version, executablePath, manifestPath] = values;
|
||||||
|
const manifest = await createLauncherManifest(version, executablePath);
|
||||||
|
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command === "stamp-info" && values.length === 2) {
|
||||||
|
const [version, infoPath] = values;
|
||||||
|
if (!versionPattern.test(version)) {
|
||||||
|
throw new Error(`Invalid launcher version: ${version}`);
|
||||||
|
}
|
||||||
|
const info = JSON.parse(await readFile(infoPath, "utf8"));
|
||||||
|
info.fixed.file_version = version;
|
||||||
|
info.info["0000"].ProductVersion = version;
|
||||||
|
await writeFile(infoPath, `${JSON.stringify(info, null, 2)}\n`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
"Usage: launcher-release.mjs next <version> | manifest <version> <exe> <output> | stamp-info <version> <info.json>",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
||||||
|
main(process.argv.slice(2)).catch((error) => {
|
||||||
|
console.error(error.message);
|
||||||
|
process.exitCode = 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import { test } from "node:test";
|
||||||
|
import { createLauncherManifest, nextLauncherVersion } from "./launcher-release.mjs";
|
||||||
|
|
||||||
|
test("increments patch digits and rolls over minor and major digits", () => {
|
||||||
|
assert.equal(nextLauncherVersion("2.1.0"), "2.1.1");
|
||||||
|
assert.equal(nextLauncherVersion("2.1.9"), "2.2.0");
|
||||||
|
assert.equal(nextLauncherVersion("2.9.9"), "3.0.0");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("rejects versions outside the three-number sequence", () => {
|
||||||
|
assert.throws(() => nextLauncherVersion("2.1"), /Invalid launcher version/);
|
||||||
|
assert.throws(() => nextLauncherVersion("02.1.0"), /Invalid launcher version/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("creates a manifest with the executable SHA-256", async () => {
|
||||||
|
const directory = await mkdtemp(join(tmpdir(), "sunbringer-release-test-"));
|
||||||
|
const executablePath = join(directory, "launcher.exe");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await writeFile(executablePath, "test executable");
|
||||||
|
const manifest = await createLauncherManifest("2.1.1", executablePath);
|
||||||
|
assert.equal(manifest.version, "2.1.1");
|
||||||
|
assert.equal(
|
||||||
|
manifest.sha256,
|
||||||
|
"ff8715f0270731bbdb0bb3586a77d032f5e883b8909dcafbf3e8909cb8c71201",
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
await rm(directory, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in new issue
Block a user