2026-10-13 23:19:22 +02:00
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 ();
2026-10-13 23:19:22 +02:00
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' );
2026-10-13 23:19:22 +02:00
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 );