Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 13219f3a6c | |||
| 0ca6802475 | |||
| caf64447b8 | |||
| 68344c6465 | |||
| 19af04468e | |||
| c96c151851 |
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "krunker-civilian-client",
|
||||
"version": "0.6.1",
|
||||
"version": "0.6.3",
|
||||
"description": "Cross-platform Krunker game client",
|
||||
"main": "dist/main/index.js",
|
||||
"homepage": "https://github.com/bigjakk/Krunker-Civilian-Client",
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
#
|
||||
# Generate Markdown release notes from conventional commits.
|
||||
# Usage: ./scripts/generate-release-notes.sh <tag> [prev-ref]
|
||||
# e.g. ./scripts/generate-release-notes.sh v0.7.0
|
||||
# e.g. ./scripts/generate-release-notes.sh v0.7.0 abc123f
|
||||
#
|
||||
# Skips version bumps, CI-only changes, and other noise.
|
||||
# If prev-ref is not provided, tries git describe to find previous tag.
|
||||
# If no previous ref is found, includes all commits up to HEAD.
|
||||
# Uses HEAD as the endpoint (tag may not exist in git yet).
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
@@ -21,32 +19,32 @@ fi
|
||||
|
||||
if [ -n "$PREV_REF" ]; then
|
||||
RANGE="${PREV_REF}..HEAD"
|
||||
COMPARE_TEXT="**Full changelog**: \`${PREV_REF}...${TAG}\`"
|
||||
else
|
||||
RANGE="HEAD"
|
||||
COMPARE_TEXT="**Initial release**"
|
||||
fi
|
||||
|
||||
# Collect commits into temp files by category
|
||||
TMPDIR_NOTES=$(mktemp -d)
|
||||
trap 'rm -rf "$TMPDIR_NOTES"' EXIT
|
||||
|
||||
for prefix in feat fix refactor perf docs test chore other; do
|
||||
for prefix in feat fix refactor perf other; do
|
||||
: > "${TMPDIR_NOTES}/${prefix}"
|
||||
done
|
||||
|
||||
while IFS= read -r line; do
|
||||
[ -z "$line" ] && continue
|
||||
|
||||
# Skip version bump commits (e.g. "v0.6.1", "v0.6.2 — description")
|
||||
[[ "$line" =~ ^v[0-9] ]] && continue
|
||||
|
||||
# Skip chore/docs/test/ci commits — not user-facing
|
||||
[[ "$line" =~ ^(chore|docs|test|ci)(\(.*\))?: ]] && continue
|
||||
|
||||
MATCHED=false
|
||||
for prefix in feat fix refactor perf docs test chore; do
|
||||
for prefix in feat fix refactor perf; do
|
||||
if [[ "$line" =~ ^${prefix}(\(.*\))?:\ (.+)$ ]]; then
|
||||
SCOPE="${BASH_REMATCH[1]}"
|
||||
MSG="${BASH_REMATCH[2]}"
|
||||
if [ -n "$SCOPE" ]; then
|
||||
echo "- **${SCOPE}**: ${MSG}" >> "${TMPDIR_NOTES}/${prefix}"
|
||||
else
|
||||
echo "- ${MSG}" >> "${TMPDIR_NOTES}/${prefix}"
|
||||
fi
|
||||
MATCHED=true
|
||||
break
|
||||
fi
|
||||
@@ -59,36 +57,34 @@ done < <(git log --format="%s" "$RANGE" 2>/dev/null)
|
||||
# Section display names
|
||||
section_title() {
|
||||
case "$1" in
|
||||
feat) echo "Features" ;;
|
||||
fix) echo "Bug Fixes" ;;
|
||||
refactor) echo "Refactoring" ;;
|
||||
perf) echo "Performance" ;;
|
||||
docs) echo "Documentation" ;;
|
||||
test) echo "Tests" ;;
|
||||
chore) echo "Chores" ;;
|
||||
feat) echo "## New" ;;
|
||||
fix) echo "## Fixes" ;;
|
||||
refactor) echo "## Improvements" ;;
|
||||
perf) echo "## Performance" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Build output
|
||||
echo "# KCC ${TAG}"
|
||||
echo ""
|
||||
# Build output — only user-facing sections
|
||||
HAS_CONTENT=false
|
||||
|
||||
for prefix in feat fix refactor perf docs test chore; do
|
||||
for prefix in feat fix refactor perf; do
|
||||
if [ -s "${TMPDIR_NOTES}/${prefix}" ]; then
|
||||
echo "## $(section_title "$prefix")"
|
||||
section_title "$prefix"
|
||||
echo ""
|
||||
cat "${TMPDIR_NOTES}/${prefix}"
|
||||
echo ""
|
||||
HAS_CONTENT=true
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -s "${TMPDIR_NOTES}/other" ]; then
|
||||
echo "## Other Changes"
|
||||
echo "## Other"
|
||||
echo ""
|
||||
cat "${TMPDIR_NOTES}/other"
|
||||
echo ""
|
||||
HAS_CONTENT=true
|
||||
fi
|
||||
|
||||
echo "---"
|
||||
echo ""
|
||||
echo "${COMPARE_TEXT}"
|
||||
if [ "$HAS_CONTENT" = false ]; then
|
||||
echo "Bug fixes and improvements."
|
||||
fi
|
||||
|
||||
+1
-1
@@ -192,7 +192,7 @@ app.whenReady().then(async () => {
|
||||
if (!cancelled && !updateWin.isDestroyed()) {
|
||||
sendProgress(`Downloading update... ${pct}%`, pct);
|
||||
}
|
||||
});
|
||||
}, update.sha256);
|
||||
|
||||
if (!cancelled) {
|
||||
sendProgress('Installing update...', 100);
|
||||
|
||||
+42
-6
@@ -1,5 +1,6 @@
|
||||
import { get as httpsGet } from 'https';
|
||||
import { createWriteStream, renameSync, unlinkSync, existsSync } from 'fs';
|
||||
import { createReadStream, createWriteStream, renameSync, unlinkSync, existsSync } from 'fs';
|
||||
import { createHash } from 'crypto';
|
||||
import { spawn } from 'child_process';
|
||||
import { app } from 'electron';
|
||||
import { electronLog } from './logger';
|
||||
@@ -8,6 +9,7 @@ export interface UpdateInfo {
|
||||
version: string;
|
||||
downloadUrl: string;
|
||||
fileSize: number;
|
||||
sha256: string;
|
||||
}
|
||||
|
||||
export type ProgressCallback = (percent: number) => void;
|
||||
@@ -15,7 +17,7 @@ export type ProgressCallback = (percent: number) => void;
|
||||
const UPDATE_CONFIG = {
|
||||
checkUrl: 'https://api.github.com/repos/bigjakk/Krunker-Civilian-Client/releases/latest',
|
||||
assetPattern: /Setup\.exe$/i,
|
||||
allowedHosts: ['github.com', 'api.github.com', 'objects.githubusercontent.com'],
|
||||
allowedHosts: ['github.com', 'githubusercontent.com'],
|
||||
};
|
||||
|
||||
const CHECK_TIMEOUT_MS = 10000;
|
||||
@@ -104,7 +106,7 @@ export function checkForUpdate(currentVersion: string): Promise<UpdateInfo | nul
|
||||
return;
|
||||
}
|
||||
|
||||
const assets: Array<{ name: string; browser_download_url: string; size: number }> = release.assets || [];
|
||||
const assets: Array<{ name: string; browser_download_url: string; size: number; digest: string }> = release.assets || [];
|
||||
const setupAsset = assets.find((a) => UPDATE_CONFIG.assetPattern.test(a.name));
|
||||
if (!setupAsset) {
|
||||
electronLog.error('[KCC-Update] No Setup.exe asset found in release', remoteVersion);
|
||||
@@ -119,11 +121,20 @@ export function checkForUpdate(currentVersion: string): Promise<UpdateInfo | nul
|
||||
return;
|
||||
}
|
||||
|
||||
electronLog.log('[KCC-Update] Update available:', remoteVersion, '| Download:', setupAsset.browser_download_url, '| Size:', setupAsset.size);
|
||||
// Extract SHA-256 digest from GitHub API (format: "sha256:<hex>")
|
||||
const sha256 = (setupAsset.digest || '').replace(/^sha256:/i, '');
|
||||
if (!sha256) {
|
||||
electronLog.error('[KCC-Update] No SHA-256 digest found for asset');
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
|
||||
electronLog.log('[KCC-Update] Update available:', remoteVersion, '| SHA-256:', sha256.substring(0, 16) + '...');
|
||||
resolve({
|
||||
version: remoteVersion,
|
||||
downloadUrl: setupAsset.browser_download_url,
|
||||
fileSize: setupAsset.size,
|
||||
sha256,
|
||||
});
|
||||
} catch (err) {
|
||||
electronLog.error('[KCC-Update] Failed to parse release data:', err);
|
||||
@@ -149,7 +160,22 @@ export function checkForUpdate(currentVersion: string): Promise<UpdateInfo | nul
|
||||
});
|
||||
}
|
||||
|
||||
export function downloadUpdate(url: string, destPath: string, onProgress: ProgressCallback): Promise<void> {
|
||||
function verifyChecksum(filePath: string, expectedSha256: string): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const hash = createHash('sha256');
|
||||
const stream = createReadStream(filePath);
|
||||
stream.on('data', (chunk) => hash.update(chunk));
|
||||
stream.on('end', () => {
|
||||
const actual = hash.digest('hex');
|
||||
electronLog.log('[KCC-Update] SHA-256 expected:', expectedSha256);
|
||||
electronLog.log('[KCC-Update] SHA-256 actual: ', actual);
|
||||
resolve(actual === expectedSha256);
|
||||
});
|
||||
stream.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
export function downloadUpdate(url: string, destPath: string, onProgress: ProgressCallback, expectedSha256?: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const tmpPath = destPath + '.tmp';
|
||||
|
||||
@@ -194,8 +220,18 @@ export function downloadUpdate(url: string, destPath: string, onProgress: Progre
|
||||
res.pipe(file);
|
||||
|
||||
file.on('finish', () => {
|
||||
file.close(() => {
|
||||
file.close(async () => {
|
||||
try {
|
||||
if (expectedSha256) {
|
||||
const valid = await verifyChecksum(tmpPath, expectedSha256);
|
||||
if (!valid) {
|
||||
electronLog.error('[KCC-Update] Checksum mismatch — file may be corrupted or tampered');
|
||||
try { unlinkSync(tmpPath); } catch { /* ignore */ }
|
||||
reject(new Error('SHA-256 checksum mismatch'));
|
||||
return;
|
||||
}
|
||||
electronLog.log('[KCC-Update] Checksum verified');
|
||||
}
|
||||
if (existsSync(destPath)) unlinkSync(destPath);
|
||||
renameSync(tmpPath, destPath);
|
||||
resolve();
|
||||
|
||||
Reference in New Issue
Block a user