diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 4d33683..d3d2833 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -136,9 +136,17 @@ jobs: if: steps.version-check.outputs.SKIP == 'false' run: npx electron-builder --linux -c.electronDist=node_modules/electron/dist-linux --publish never - - name: Build asar for patch updates + - name: Extract asar for patch updates if: steps.version-check.outputs.SKIP == 'false' && steps.release-type.outputs.TYPE == 'patch' - run: npm run build:asar + run: | + # The asar is a byproduct of the Linux build — platform-independent (just JS) + ASAR_SRC="out/linux-unpacked/resources/app.asar" + ASAR_DIR="out/asar" + mkdir -p "$ASAR_DIR" + cp "$ASAR_SRC" "$ASAR_DIR/app.asar" + sha256sum "$ASAR_DIR/app.asar" | awk '{print $1 " app.asar"}' > "$ASAR_DIR/checksums.sha256" + echo "Extracted asar: $(du -h "$ASAR_DIR/app.asar" | cut -f1)" + cat "$ASAR_DIR/checksums.sha256" - name: Report build sizes if: steps.version-check.outputs.SKIP == 'false' diff --git a/package-lock.json b/package-lock.json index a413277..e08b46e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,18 @@ { "name": "krunker-civilian-client", - "version": "0.7.5", + "version": "0.7.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "krunker-civilian-client", - "version": "0.7.5", + "version": "0.7.9", "hasInstallScript": true, "license": "GPL-3.0", "dependencies": { "electron-store": "^8.2.0" }, "devDependencies": { - "@electron/asar": "^4.2.0", "@eslint/js": "^10.0.1", "@types/node": "^22.0.0", "electron": "npm:electron-nightly@42.0.0-nightly.20260227", @@ -45,69 +44,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/@electron/asar": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-4.2.0.tgz", - "integrity": "sha512-npW1NW5yy8EB9XY/vEw9sUdgmq0sJEhmSBb6bqyFOAw1CSkrhvAvO6QWlW8CdIMo8VN1lkdF345l/MeW0LrY0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "commander": "^13.1.0", - "glob": "^13.0.2", - "minimatch": "^10.0.1" - }, - "bin": { - "asar": "bin/asar.mjs" - }, - "engines": { - "node": ">=22.12.0" - } - }, - "node_modules/@electron/asar/node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@electron/asar/node_modules/lru-cache": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", - "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@electron/asar/node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@electron/fuses": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@electron/fuses/-/fuses-1.8.0.tgz", @@ -3157,16 +3093,6 @@ "node": ">= 0.8" } }, - "node_modules/commander": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", - "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/compare-version": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", diff --git a/package.json b/package.json index 9afe70a..5bf3c51 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "dist:win": "npm run build && electron-builder --win", "dist:linux": "npm run build && electron-builder --linux", "dist:all": "npm run build && electron-builder --win --linux", - "build:asar": "node scripts/build-asar.js", "clean": "rimraf dist out", "lint": "eslint src/", "prepare": "husky" @@ -29,7 +28,6 @@ "electron-store": "^8.2.0" }, "devDependencies": { - "@electron/asar": "^4.2.0", "@eslint/js": "^10.0.1", "@types/node": "^22.0.0", "electron": "npm:electron-nightly@42.0.0-nightly.20260227", diff --git a/scripts/build-asar.js b/scripts/build-asar.js deleted file mode 100644 index e4cc005..0000000 --- a/scripts/build-asar.js +++ /dev/null @@ -1,146 +0,0 @@ -/** - * Build a standalone app.asar + checksums.sha256 for minor (patch) updates. - * - * Replicates what electron-builder packs into the asar (dist/ + package.json + - * required node_modules) without running the full installer build. - * - * Automatically resolves transitive dependencies so sub-deps are never missed. - * - * Usage: - * npm run build:asar # default (github) update source - * UPDATE_SOURCE=gitea npm run build:asar # gitea update source - */ - -const { execSync } = require('child_process'); -const { cpSync, mkdirSync, rmSync, readFileSync, writeFileSync, createReadStream, existsSync } = require('fs'); -const { join } = require('path'); -const { createHash } = require('crypto'); -const asar = require('@electron/asar'); - -const ROOT = join(__dirname, '..'); -const STAGING = join(ROOT, 'out', 'asar-staging'); -const OUTPUT = join(ROOT, 'out', 'asar'); - -// Top-level packages to include (same as electron-builder.yml) -const SEED_MODULES = [ - 'electron-store', 'conf', 'dot-prop', 'type-fest', 'pkg-up', - 'find-up', 'locate-path', 'p-locate', 'p-limit', 'yocto-queue', - 'path-exists', 'env-paths', 'json-schema-typed', 'ajv', 'ajv-formats', - '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) { - return src.endsWith('.ts') || src.endsWith('.map'); -} - -async function main() { - // 1. Run Vite build (pass UPDATE_SOURCE through env) - const env = { ...process.env }; - if (env.UPDATE_SOURCE) { - console.log(`Building with UPDATE_SOURCE=${env.UPDATE_SOURCE}`); - } - execSync('npm run build', { cwd: ROOT, stdio: 'inherit', env }); - - // 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 }); - mkdirSync(STAGING, { recursive: true }); - - // Copy dist/ - cpSync(join(ROOT, 'dist'), join(STAGING, 'dist'), { recursive: true }); - - // Copy package.json (Electron reads "main" from it) - cpSync(join(ROOT, 'package.json'), join(STAGING, 'package.json')); - - // Copy all resolved node_modules (excluding .ts and .map files) - for (const mod of allModules) { - const src = join(ROOT, '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, { - recursive: true, - filter: (s) => !shouldExclude(s), - }); - } - } - - // 4. Pack into asar - mkdirSync(OUTPUT, { recursive: true }); - const asarPath = join(OUTPUT, 'app.asar'); - await asar.createPackage(STAGING, asarPath); - console.log('\nCreated:', asarPath); - - // 5. Generate checksums.sha256 - const hex = await fileHash(asarPath); - const checksumsPath = join(OUTPUT, 'checksums.sha256'); - writeFileSync(checksumsPath, `${hex} app.asar\n`); - console.log(`SHA-256: ${hex}`); - console.log('Created:', checksumsPath); - - // 6. Cleanup staging - rmSync(STAGING, { recursive: true, force: true }); - - // Print size and module list - const size = readFileSync(asarPath).length; - console.log(`\napp.asar size: ${(size / 1024).toFixed(1)} KB`); - console.log('Modules:', [...allModules].sort().join(', ')); -} - -function fileHash(filePath) { - return new Promise((resolve, reject) => { - const hash = createHash('sha256'); - const stream = createReadStream(filePath); - stream.on('data', (chunk) => hash.update(chunk)); - stream.on('end', () => resolve(hash.digest('hex'))); - stream.on('error', reject); - }); -} - -main().catch((err) => { - console.error('build-asar failed:', err); - process.exit(1); -});