Compare commits
7 Commits
v0.7.6
...
2cf5623186
| Author | SHA1 | Date | |
|---|---|---|---|
| 2cf5623186 | |||
| 5e7786d66e | |||
| 9a52d6b8fc | |||
| 255befd1b8 | |||
| 691b363f88 | |||
| 8972c3e363 | |||
| 5a8d77c494 |
@@ -75,7 +75,7 @@ jobs:
|
|||||||
CHANGED=$(git diff --name-only "$PREV_TAG"..HEAD)
|
CHANGED=$(git diff --name-only "$PREV_TAG"..HEAD)
|
||||||
echo "$CHANGED"
|
echo "$CHANGED"
|
||||||
|
|
||||||
# Files that require a full (installer) build
|
# Files that require a full (installer-only) release
|
||||||
FULL_TRIGGERS="electron-builder.yml|scripts/download-electron.js"
|
FULL_TRIGGERS="electron-builder.yml|scripts/download-electron.js"
|
||||||
|
|
||||||
if echo "$CHANGED" | grep -qE "^($FULL_TRIGGERS)$"; then
|
if echo "$CHANGED" | grep -qE "^($FULL_TRIGGERS)$"; then
|
||||||
@@ -105,13 +105,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: '22'
|
node-version: '22'
|
||||||
|
|
||||||
# Full build needs wine + system deps for electron-builder
|
|
||||||
- name: Install system dependencies
|
- name: Install system dependencies
|
||||||
if: steps.version-check.outputs.SKIP == 'false' && steps.release-type.outputs.TYPE == 'full'
|
if: steps.version-check.outputs.SKIP == 'false'
|
||||||
run: |
|
run: |
|
||||||
sudo dpkg --add-architecture i386
|
dpkg --add-architecture i386
|
||||||
sudo apt-get update -qq
|
apt-get update -qq
|
||||||
sudo apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
wine wine32 wine64 \
|
wine wine32 wine64 \
|
||||||
libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2t64 \
|
libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2t64 \
|
||||||
libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 \
|
libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 \
|
||||||
@@ -123,39 +122,29 @@ jobs:
|
|||||||
if: steps.version-check.outputs.SKIP == 'false'
|
if: steps.version-check.outputs.SKIP == 'false'
|
||||||
run: npm ci --legacy-peer-deps
|
run: npm ci --legacy-peer-deps
|
||||||
|
|
||||||
# ── Patch release: asar only ──
|
|
||||||
|
|
||||||
- name: Build asar
|
|
||||||
if: steps.version-check.outputs.SKIP == 'false' && steps.release-type.outputs.TYPE == 'patch'
|
|
||||||
run: npm run build:asar
|
|
||||||
|
|
||||||
# ── Full release: installer + asar ──
|
|
||||||
|
|
||||||
- name: Build source
|
- name: Build source
|
||||||
if: steps.version-check.outputs.SKIP == 'false' && steps.release-type.outputs.TYPE == 'full'
|
if: steps.version-check.outputs.SKIP == 'false'
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
|
||||||
- name: Build Windows distributables
|
- name: Build Windows distributables
|
||||||
if: steps.version-check.outputs.SKIP == 'false' && steps.release-type.outputs.TYPE == 'full'
|
if: steps.version-check.outputs.SKIP == 'false'
|
||||||
env:
|
env:
|
||||||
WINEDEBUG: "-all"
|
WINEDEBUG: "-all"
|
||||||
run: npx electron-builder --win -c.electronDist=node_modules/electron/dist-win --publish never
|
run: npx electron-builder --win -c.electronDist=node_modules/electron/dist-win --publish never
|
||||||
|
|
||||||
- name: Build Linux distributables
|
- name: Build Linux distributables
|
||||||
if: steps.version-check.outputs.SKIP == 'false' && steps.release-type.outputs.TYPE == 'full'
|
if: steps.version-check.outputs.SKIP == 'false'
|
||||||
run: npx electron-builder --linux -c.electronDist=node_modules/electron/dist-linux --publish never
|
run: npx electron-builder --linux -c.electronDist=node_modules/electron/dist-linux --publish never
|
||||||
|
|
||||||
- name: Build asar for full release
|
- name: Build asar for patch updates
|
||||||
if: steps.version-check.outputs.SKIP == 'false' && steps.release-type.outputs.TYPE == 'full'
|
if: steps.version-check.outputs.SKIP == 'false' && steps.release-type.outputs.TYPE == 'patch'
|
||||||
run: npm run build:asar
|
run: npm run build:asar
|
||||||
|
|
||||||
- name: Report build sizes
|
- name: Report build sizes
|
||||||
if: steps.version-check.outputs.SKIP == 'false'
|
if: steps.version-check.outputs.SKIP == 'false'
|
||||||
run: |
|
run: |
|
||||||
echo "=== Release type: ${{ steps.release-type.outputs.TYPE }} ==="
|
echo "=== Release type: ${{ steps.release-type.outputs.TYPE }} ==="
|
||||||
if [ "${{ steps.release-type.outputs.TYPE }}" = "full" ]; then
|
|
||||||
ls -lh out/*.exe out/*.AppImage out/*.deb 2>/dev/null || true
|
ls -lh out/*.exe out/*.AppImage out/*.deb 2>/dev/null || true
|
||||||
fi
|
|
||||||
ls -lh out/asar/* 2>/dev/null || true
|
ls -lh out/asar/* 2>/dev/null || true
|
||||||
|
|
||||||
- name: Generate release notes
|
- name: Generate release notes
|
||||||
@@ -215,7 +204,8 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Upload asar + checksums (both patch and full releases)
|
# Upload asar + checksums (patch releases only — signals client to use fast path)
|
||||||
|
if [ "$RELEASE_TYPE" = "patch" ]; then
|
||||||
for file in out/asar/app.asar out/asar/checksums.sha256; do
|
for file in out/asar/app.asar out/asar/checksums.sha256; do
|
||||||
[ -f "$file" ] || continue
|
[ -f "$file" ] || continue
|
||||||
FILENAME=$(basename "$file")
|
FILENAME=$(basename "$file")
|
||||||
@@ -226,9 +216,11 @@ jobs:
|
|||||||
-F "attachment=@$file" \
|
-F "attachment=@$file" \
|
||||||
| jq -r '" -> \(.name) (\(.size) bytes)"'
|
| jq -r '" -> \(.name) (\(.size) bytes)"'
|
||||||
done
|
done
|
||||||
|
else
|
||||||
|
echo "Full release — skipping asar upload (forces installer update)"
|
||||||
|
fi
|
||||||
|
|
||||||
# Upload installer artifacts (full release only)
|
# Upload installers (always — for new users and major updates)
|
||||||
if [ "$RELEASE_TYPE" = "full" ]; then
|
|
||||||
for file in out/*.exe out/*.AppImage out/*.deb; do
|
for file in out/*.exe out/*.AppImage out/*.deb; do
|
||||||
[ -f "$file" ] || continue
|
[ -f "$file" ] || continue
|
||||||
FILENAME=$(basename "$file")
|
FILENAME=$(basename "$file")
|
||||||
@@ -240,9 +232,8 @@ jobs:
|
|||||||
-F "attachment=@$file" \
|
-F "attachment=@$file" \
|
||||||
| jq -r '" -> \(.name) (\(.size) bytes)"'
|
| jq -r '" -> \(.name) (\(.size) bytes)"'
|
||||||
done
|
done
|
||||||
fi
|
|
||||||
|
|
||||||
echo "All assets uploaded (release type: $RELEASE_TYPE)"
|
echo "All assets uploaded"
|
||||||
|
|
||||||
- name: Prune old releases
|
- name: Prune old releases
|
||||||
if: steps.version-check.outputs.SKIP == 'false'
|
if: steps.version-check.outputs.SKIP == 'false'
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "krunker-civilian-client",
|
"name": "krunker-civilian-client",
|
||||||
"version": "0.7.6",
|
"version": "0.7.9",
|
||||||
"description": "Cross-platform Krunker game client",
|
"description": "Cross-platform Krunker game client",
|
||||||
"main": "dist/main/index.js",
|
"main": "dist/main/index.js",
|
||||||
"homepage": "https://github.com/bigjakk/Krunker-Civilian-Client",
|
"homepage": "https://github.com/bigjakk/Krunker-Civilian-Client",
|
||||||
|
|||||||
+61
-11
@@ -4,13 +4,15 @@
|
|||||||
* Replicates what electron-builder packs into the asar (dist/ + package.json +
|
* Replicates what electron-builder packs into the asar (dist/ + package.json +
|
||||||
* required node_modules) without running the full installer build.
|
* required node_modules) without running the full installer build.
|
||||||
*
|
*
|
||||||
|
* Automatically resolves transitive dependencies so sub-deps are never missed.
|
||||||
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* npm run build:asar # default (github) update source
|
* npm run build:asar # default (github) update source
|
||||||
* UPDATE_SOURCE=gitea npm run build:asar # gitea update source
|
* UPDATE_SOURCE=gitea npm run build:asar # gitea update source
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { execSync } = require('child_process');
|
const { execSync } = require('child_process');
|
||||||
const { cpSync, mkdirSync, rmSync, readFileSync, writeFileSync, createReadStream } = require('fs');
|
const { cpSync, mkdirSync, rmSync, readFileSync, writeFileSync, createReadStream, existsSync } = require('fs');
|
||||||
const { join } = require('path');
|
const { join } = require('path');
|
||||||
const { createHash } = require('crypto');
|
const { createHash } = require('crypto');
|
||||||
const asar = require('@electron/asar');
|
const asar = require('@electron/asar');
|
||||||
@@ -19,14 +21,51 @@ const ROOT = join(__dirname, '..');
|
|||||||
const STAGING = join(ROOT, 'out', 'asar-staging');
|
const STAGING = join(ROOT, 'out', 'asar-staging');
|
||||||
const OUTPUT = join(ROOT, 'out', 'asar');
|
const OUTPUT = join(ROOT, 'out', 'asar');
|
||||||
|
|
||||||
// Same node_modules list as electron-builder.yml
|
// Top-level packages to include (same as electron-builder.yml)
|
||||||
const NODE_MODULES = [
|
const SEED_MODULES = [
|
||||||
'electron-store', 'conf', 'dot-prop', 'type-fest', 'pkg-up',
|
'electron-store', 'conf', 'dot-prop', 'type-fest', 'pkg-up',
|
||||||
'find-up', 'locate-path', 'p-locate', 'p-limit', 'yocto-queue',
|
'find-up', 'locate-path', 'p-locate', 'p-limit', 'yocto-queue',
|
||||||
'path-exists', 'env-paths', 'json-schema-typed', 'ajv', 'ajv-formats',
|
'path-exists', 'env-paths', 'json-schema-typed', 'ajv', 'ajv-formats',
|
||||||
'atomically', 'debounce-fn', 'mimic-fn', 'semver', 'onetime',
|
'atomically', 'debounce-fn', 'mimic-fn', 'semver', 'onetime',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Walk the dependency tree starting from seed modules and collect all
|
||||||
|
* transitive production dependencies.
|
||||||
|
*/
|
||||||
|
function resolveAllDeps(seeds) {
|
||||||
|
const resolved = new Set();
|
||||||
|
const queue = [...seeds];
|
||||||
|
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const mod = queue.shift();
|
||||||
|
if (resolved.has(mod)) continue;
|
||||||
|
|
||||||
|
// Check if module exists in top-level node_modules
|
||||||
|
const modDir = join(ROOT, 'node_modules', mod);
|
||||||
|
if (!existsSync(modDir)) {
|
||||||
|
console.warn(` Warning: ${mod} not found in node_modules, skipping`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolved.add(mod);
|
||||||
|
|
||||||
|
// Read its dependencies and enqueue them
|
||||||
|
const pkgPath = join(modDir, 'package.json');
|
||||||
|
if (existsSync(pkgPath)) {
|
||||||
|
try {
|
||||||
|
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
||||||
|
const deps = Object.keys(pkg.dependencies || {});
|
||||||
|
for (const dep of deps) {
|
||||||
|
if (!resolved.has(dep)) queue.push(dep);
|
||||||
|
}
|
||||||
|
} catch { /* ignore malformed package.json */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
function shouldExclude(src) {
|
function shouldExclude(src) {
|
||||||
return src.endsWith('.ts') || src.endsWith('.map');
|
return src.endsWith('.ts') || src.endsWith('.map');
|
||||||
}
|
}
|
||||||
@@ -39,7 +78,12 @@ async function main() {
|
|||||||
}
|
}
|
||||||
execSync('npm run build', { cwd: ROOT, stdio: 'inherit', env });
|
execSync('npm run build', { cwd: ROOT, stdio: 'inherit', env });
|
||||||
|
|
||||||
// 2. Prepare staging directory
|
// 2. Resolve all dependencies (including transitive)
|
||||||
|
console.log('\nResolving dependencies...');
|
||||||
|
const allModules = resolveAllDeps(SEED_MODULES);
|
||||||
|
console.log(`Found ${allModules.size} modules (${SEED_MODULES.length} seed + ${allModules.size - SEED_MODULES.length} transitive)`);
|
||||||
|
|
||||||
|
// 3. Prepare staging directory
|
||||||
rmSync(STAGING, { recursive: true, force: true });
|
rmSync(STAGING, { recursive: true, force: true });
|
||||||
mkdirSync(STAGING, { recursive: true });
|
mkdirSync(STAGING, { recursive: true });
|
||||||
|
|
||||||
@@ -49,35 +93,41 @@ async function main() {
|
|||||||
// Copy package.json (Electron reads "main" from it)
|
// Copy package.json (Electron reads "main" from it)
|
||||||
cpSync(join(ROOT, 'package.json'), join(STAGING, 'package.json'));
|
cpSync(join(ROOT, 'package.json'), join(STAGING, 'package.json'));
|
||||||
|
|
||||||
// Copy required node_modules (excluding .ts and .map files)
|
// Copy all resolved node_modules (excluding .ts and .map files)
|
||||||
for (const mod of NODE_MODULES) {
|
for (const mod of allModules) {
|
||||||
const src = join(ROOT, 'node_modules', mod);
|
const src = join(ROOT, 'node_modules', mod);
|
||||||
const dest = join(STAGING, 'node_modules', mod);
|
const dest = join(STAGING, 'node_modules', mod);
|
||||||
|
|
||||||
|
// Some packages have nested node_modules with their own deps
|
||||||
|
// (hoisted vs non-hoisted). Check both the top-level and nested locations.
|
||||||
|
if (existsSync(src)) {
|
||||||
cpSync(src, dest, {
|
cpSync(src, dest, {
|
||||||
recursive: true,
|
recursive: true,
|
||||||
filter: (s) => !shouldExclude(s),
|
filter: (s) => !shouldExclude(s),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 3. Pack into asar
|
// 4. Pack into asar
|
||||||
mkdirSync(OUTPUT, { recursive: true });
|
mkdirSync(OUTPUT, { recursive: true });
|
||||||
const asarPath = join(OUTPUT, 'app.asar');
|
const asarPath = join(OUTPUT, 'app.asar');
|
||||||
await asar.createPackage(STAGING, asarPath);
|
await asar.createPackage(STAGING, asarPath);
|
||||||
console.log('Created:', asarPath);
|
console.log('\nCreated:', asarPath);
|
||||||
|
|
||||||
// 4. Generate checksums.sha256
|
// 5. Generate checksums.sha256
|
||||||
const hex = await fileHash(asarPath);
|
const hex = await fileHash(asarPath);
|
||||||
const checksumsPath = join(OUTPUT, 'checksums.sha256');
|
const checksumsPath = join(OUTPUT, 'checksums.sha256');
|
||||||
writeFileSync(checksumsPath, `${hex} app.asar\n`);
|
writeFileSync(checksumsPath, `${hex} app.asar\n`);
|
||||||
console.log(`SHA-256: ${hex}`);
|
console.log(`SHA-256: ${hex}`);
|
||||||
console.log('Created:', checksumsPath);
|
console.log('Created:', checksumsPath);
|
||||||
|
|
||||||
// 5. Cleanup staging
|
// 6. Cleanup staging
|
||||||
rmSync(STAGING, { recursive: true, force: true });
|
rmSync(STAGING, { recursive: true, force: true });
|
||||||
|
|
||||||
// Print size
|
// Print size and module list
|
||||||
const size = readFileSync(asarPath).length;
|
const size = readFileSync(asarPath).length;
|
||||||
console.log(`\napp.asar size: ${(size / 1024).toFixed(1)} KB`);
|
console.log(`\napp.asar size: ${(size / 1024).toFixed(1)} KB`);
|
||||||
|
console.log('Modules:', [...allModules].sort().join(', '));
|
||||||
}
|
}
|
||||||
|
|
||||||
function fileHash(filePath) {
|
function fileHash(filePath) {
|
||||||
|
|||||||
+6
-5
@@ -1,6 +1,7 @@
|
|||||||
import { app, BrowserWindow, Menu, clipboard, ipcMain, safeStorage, session, shell } from 'electron';
|
import { app, BrowserWindow, Menu, clipboard, ipcMain, safeStorage, session, shell } from 'electron';
|
||||||
import { join, dirname } from 'path';
|
import { join, dirname } from 'path';
|
||||||
import { existsSync, mkdirSync, unlinkSync, promises as fsp } from 'fs';
|
import { existsSync, mkdirSync, promises as fsp } from 'fs';
|
||||||
|
import { existsSync as origExistsSync, unlinkSync as origUnlinkSync } from 'original-fs';
|
||||||
import { get as httpsGet } from 'https';
|
import { get as httpsGet } from 'https';
|
||||||
import { execFile } from 'child_process';
|
import { execFile } from 'child_process';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
@@ -179,8 +180,8 @@ app.whenReady().then(async () => {
|
|||||||
// Clean up stale pending asar from a previous failed swap
|
// Clean up stale pending asar from a previous failed swap
|
||||||
const resourcesDir = join(dirname(app.getPath('exe')), 'resources');
|
const resourcesDir = join(dirname(app.getPath('exe')), 'resources');
|
||||||
const stalePending = join(resourcesDir, 'app-pending.asar');
|
const stalePending = join(resourcesDir, 'app-pending.asar');
|
||||||
if (existsSync(stalePending)) {
|
if (origExistsSync(stalePending)) {
|
||||||
try { unlinkSync(stalePending); } catch { /* ignore */ }
|
try { origUnlinkSync(stalePending); } catch { /* ignore */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -213,8 +214,8 @@ app.whenReady().then(async () => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
electronLog.error('[KCC] Patch download failed:', err);
|
electronLog.error('[KCC] Patch download failed:', err);
|
||||||
// Clean up failed download
|
// Clean up failed download
|
||||||
if (existsSync(pendingPath)) {
|
if (origExistsSync(pendingPath)) {
|
||||||
try { unlinkSync(pendingPath); } catch { /* ignore */ }
|
try { origUnlinkSync(pendingPath); } catch { /* ignore */ }
|
||||||
}
|
}
|
||||||
if (!updateWin.isDestroyed()) updateWin.close();
|
if (!updateWin.isDestroyed()) updateWin.close();
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-4
@@ -1,5 +1,7 @@
|
|||||||
import { get as httpsGet } from 'https';
|
import { get as httpsGet } from 'https';
|
||||||
import { createReadStream, createWriteStream, writeFileSync, renameSync, unlinkSync, existsSync, mkdirSync } from 'fs';
|
// Use original-fs to bypass Electron's asar interception — required for
|
||||||
|
// writing/renaming .asar files in the resources directory.
|
||||||
|
import { createReadStream, createWriteStream, writeFileSync, renameSync, unlinkSync, existsSync, mkdirSync } from 'original-fs';
|
||||||
import { join, dirname } from 'path';
|
import { join, dirname } from 'path';
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
@@ -78,12 +80,12 @@ if (-not (Test-Path $pending)) { exit 1 }
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (Test-Path $backup) { Remove-Item $backup -Force }
|
if (Test-Path $backup) { Remove-Item $backup -Force }
|
||||||
Rename-Item $asar $backup -Force
|
Move-Item $asar $backup -Force
|
||||||
Rename-Item $pending $asar -Force
|
Move-Item $pending $asar -Force
|
||||||
if (Test-Path $backup) { Remove-Item $backup -Force }
|
if (Test-Path $backup) { Remove-Item $backup -Force }
|
||||||
} catch {
|
} catch {
|
||||||
if ((Test-Path $backup) -and -not (Test-Path $asar)) {
|
if ((Test-Path $backup) -and -not (Test-Path $asar)) {
|
||||||
Rename-Item $backup $asar -Force
|
Move-Item $backup $asar -Force
|
||||||
}
|
}
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ export default defineConfig({
|
|||||||
outDir: 'dist/main',
|
outDir: 'dist/main',
|
||||||
emptyDirBefore: true,
|
emptyDirBefore: true,
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
external: ['electron', 'electron-store', ...nodeBuiltins],
|
external: ['electron', 'electron-store', 'original-fs', ...nodeBuiltins],
|
||||||
},
|
},
|
||||||
target: 'node20',
|
target: 'node20',
|
||||||
minify: isProd,
|
minify: isProd,
|
||||||
|
|||||||
Reference in New Issue
Block a user