Files
safetynet/scripts/package.mjs
T

26 lines
3.3 KiB
JavaScript
Raw Normal View History

import {hardenedElectronFuses,verifyPackagedFuses} from './package-fuses.mjs';
2026-10-13 10:33:31 +02:00
import { spawnSync } from 'node:child_process';
import {readFileSync,readdirSync} from 'node:fs';
import {createHash} from 'node:crypto';
import {createRequire} from 'node:module';
import path from 'node:path';
const platform=process.argv[2];if(platform!=='win'||process.platform!=='win32')throw Error('Windows is the active build target; other platforms are deferred');
const runtime=spawnSync(process.env.SAFETYNET_BUILD_PYTHON||'python',['scripts/provision-windows-runtime.py'],{stdio:'inherit'});if(runtime.status!==0)process.exit(runtime.status??1);
const semantic=spawnSync(process.env.SAFETYNET_BUILD_PYTHON||'python',['scripts/provision-semantic-runtime.py','--verify'],{stdio:'inherit'});if(semantic.status!==0)process.exit(semantic.status??1);
const name=process.env.BRAND_NAME||'SafetyNet',id=process.env.BRAND_APP_ID||'enterprise.safetynet.browser';
if(!/^[\w .-]{1,40}$/.test(name)||!/^[a-z][a-z0-9]*(?:\.[a-z][a-z0-9]*){2,}$/.test(id))throw Error('Invalid white-label product name or app ID');
const configuration=JSON.parse(readFileSync('package.json','utf8')),workspace=path.resolve('.'),output=path.resolve(configuration.build.directories.output);
if(!output.startsWith(workspace+path.sep))throw Error('Build output must stay inside the workspace');
// Reuse the pinned installed distribution already exercised by native tests.
// This avoids a second archive extraction and Windows directory-rename locks.
const electronDirectory=path.dirname(createRequire(import.meta.url)('electron'));
if(readFileSync(path.join(electronDirectory,'version'),'utf8').trim().replace(/^v/,'')!==configuration.devDependencies.electron)throw Error('Installed Electron does not match the build pin');
function sourceSnapshot(){const entries=[];function walk(directory){for(const item of readdirSync(directory,{withFileTypes:true})){const file=path.join(directory,item.name);if(item.isDirectory())walk(file);else if(item.isFile())entries.push([file,createHash('sha256').update(readFileSync(file)).digest('hex')]);}}for(const directory of ['apps/browser','packages/core'])walk(directory);entries.push(['package.json',createHash('sha256').update(readFileSync('package.json')).digest('hex')]);return JSON.stringify(entries.sort((a,b)=>a[0].localeCompare(b[0])));}
const sourcesBefore=sourceSnapshot();
const r=spawnSync(process.execPath,['node_modules/electron-builder/out/cli/cli.js',`--${platform}`,'--dir',`--config.electronDist=${electronDirectory}`,`--config.productName=${name}`,`--config.appId=${id}`,...Object.entries(hardenedElectronFuses).map(([key,value])=>`--config.electronFuses.${key}=${value}`)],{stdio:'inherit',env:process.env});
2026-10-13 10:33:31 +02:00
if(r.status!==0)process.exit(r.status??1);
if(sourceSnapshot()!==sourcesBefore)throw Error('Browser sources changed during packaging; discard this artifact and rebuild from stable sources');
const resources=path.join(output,'win-unpacked/resources');
await verifyPackagedFuses(path.join(output,'win-unpacked',`${name}.exe`),path.join(resources,'app.asar'));
console.log('Packaged Electron security fuses and embedded ASAR integrity verified.');
2026-10-13 10:33:31 +02:00
const acl=spawnSync(path.join(resources,'python/python.exe'),[path.join(resources,'inspector/provision_runtime_acl.py'),'--provision'],{stdio:'inherit',windowsHide:true});process.exit(acl.status??1);