39 lines
3.0 KiB
JavaScript
39 lines
3.0 KiB
JavaScript
// Not wired into package.mjs until a real packaged broker is available.
|
|
// Official CLI: https://www.electron.build/docs/cli/
|
|
// --dir produces only an unpacked app; --win nsis --prepackaged distributes
|
|
// that verified directory without silently rebuilding its application payload.
|
|
import path from 'node:path';
|
|
|
|
function fail(code){throw Object.assign(new Error(code),{code});}
|
|
function identity(value){
|
|
if(!value||!['sourceSha256','runtimeSha256','packageSha256'].every(key=>/^[a-f0-9]{64}$/.test(value[key])))fail('package_identity_invalid');
|
|
return Object.freeze(Object.fromEntries(['sourceSha256','runtimeSha256','packageSha256'].map(key=>[key,value[key]])));
|
|
}
|
|
function same(left,right){return Object.keys(left).every(key=>left[key]===right[key]);}
|
|
export function createWindowsPackagePlan({workspace,outputDirectory,electronDirectory,productName='SafetyNet',appId='enterprise.safetynet.browser',platform=process.platform}){
|
|
if(platform!=='win32')fail('package_platform_unsupported');
|
|
if(![workspace,outputDirectory,electronDirectory].every(value=>typeof value==='string'&&path.isAbsolute(value)))fail('package_path_invalid');
|
|
const root=path.resolve(workspace),output=path.resolve(outputDirectory),runtime=path.resolve(electronDirectory);
|
|
const relative=path.relative(root,output);
|
|
if(!relative||relative.startsWith('..'+path.sep)||relative==='..'||path.isAbsolute(relative))fail('package_output_unsafe');
|
|
if(!/^[\w .-]{1,40}$/.test(productName)||!/^[a-z][a-z0-9]*(?:\.[a-z][a-z0-9]*){2,}$/.test(appId))fail('package_brand_invalid');
|
|
const directory=path.join(output,'win-unpacked');
|
|
const common=[`--config.productName=${productName}`,`--config.appId=${appId}`,`--config.directories.output=${output}`,'--publish','never'];
|
|
return Object.freeze({
|
|
unpacked:Object.freeze({stage:'build-unpacked',args:Object.freeze(['--win','--dir',`--config.electronDist=${runtime}`,...common]),directory}),
|
|
verification:Object.freeze({stage:'verify-unpacked',required:Object.freeze(['fuses','resources','broker','runtimeAcl']),directory}),
|
|
async installer({expectedIdentity,readIdentity,verify}){
|
|
if(typeof readIdentity!=='function'||typeof verify!=='function')fail('package_verifier_required');
|
|
const expected=identity(expectedIdentity);
|
|
if(!same(expected,identity(await readIdentity(directory))))fail('package_identity_changed');
|
|
const receipt=await verify(directory,expected);
|
|
if(!receipt||!same(expected,identity(receipt.identity))||!['fuses','resources','broker','runtimeAcl'].every(key=>receipt[key]===true))fail('package_verification_incomplete');
|
|
if(!same(expected,identity(await readIdentity(directory))))fail('package_identity_changed');
|
|
// This plans arguments only. Caller must protect the verified directory
|
|
// against changes through invocation and inspect the real installer result.
|
|
return Object.freeze({stage:'build-installer',directory,identity:expected,
|
|
args:Object.freeze(['--win','nsis','--prepackaged',directory,...common])});
|
|
}
|
|
});
|
|
}
|