refactor: extract asar from electron-builder output instead of custom script
This commit is contained in:
@@ -136,9 +136,17 @@ jobs:
|
|||||||
if: steps.version-check.outputs.SKIP == 'false'
|
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 patch updates
|
- name: Extract asar for patch updates
|
||||||
if: steps.version-check.outputs.SKIP == 'false' && steps.release-type.outputs.TYPE == 'patch'
|
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
|
- name: Report build sizes
|
||||||
if: steps.version-check.outputs.SKIP == 'false'
|
if: steps.version-check.outputs.SKIP == 'false'
|
||||||
|
|||||||
Generated
+2
-76
@@ -1,19 +1,18 @@
|
|||||||
{
|
{
|
||||||
"name": "krunker-civilian-client",
|
"name": "krunker-civilian-client",
|
||||||
"version": "0.7.5",
|
"version": "0.7.9",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "krunker-civilian-client",
|
"name": "krunker-civilian-client",
|
||||||
"version": "0.7.5",
|
"version": "0.7.9",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"electron-store": "^8.2.0"
|
"electron-store": "^8.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@electron/asar": "^4.2.0",
|
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
"@types/node": "^22.0.0",
|
"@types/node": "^22.0.0",
|
||||||
"electron": "npm:electron-nightly@42.0.0-nightly.20260227",
|
"electron": "npm:electron-nightly@42.0.0-nightly.20260227",
|
||||||
@@ -45,69 +44,6 @@
|
|||||||
"url": "https://opencollective.com/webpack"
|
"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": {
|
"node_modules/@electron/fuses": {
|
||||||
"version": "1.8.0",
|
"version": "1.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/@electron/fuses/-/fuses-1.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/@electron/fuses/-/fuses-1.8.0.tgz",
|
||||||
@@ -3157,16 +3093,6 @@
|
|||||||
"node": ">= 0.8"
|
"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": {
|
"node_modules/compare-version": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz",
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
"dist:win": "npm run build && electron-builder --win",
|
"dist:win": "npm run build && electron-builder --win",
|
||||||
"dist:linux": "npm run build && electron-builder --linux",
|
"dist:linux": "npm run build && electron-builder --linux",
|
||||||
"dist:all": "npm run build && electron-builder --win --linux",
|
"dist:all": "npm run build && electron-builder --win --linux",
|
||||||
"build:asar": "node scripts/build-asar.js",
|
|
||||||
"clean": "rimraf dist out",
|
"clean": "rimraf dist out",
|
||||||
"lint": "eslint src/",
|
"lint": "eslint src/",
|
||||||
"prepare": "husky"
|
"prepare": "husky"
|
||||||
@@ -29,7 +28,6 @@
|
|||||||
"electron-store": "^8.2.0"
|
"electron-store": "^8.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@electron/asar": "^4.2.0",
|
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
"@types/node": "^22.0.0",
|
"@types/node": "^22.0.0",
|
||||||
"electron": "npm:electron-nightly@42.0.0-nightly.20260227",
|
"electron": "npm:electron-nightly@42.0.0-nightly.20260227",
|
||||||
|
|||||||
@@ -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);
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user