Reference source · 0.1.0-dev.11
validator-lock.ts
src/validator-lock.ts · Download original file
import { createHash } from 'node:crypto';
import { lstatSync,readdirSync,readFileSync,existsSync } from 'node:fs';
import { join } from 'node:path';
import { requireThat } from './core.ts';
export type PackageTree={name:string;version:string;tree_sha256:string;files:number;bytes:number};
export type DependencyLock={format:1;configuration_sha256:string|null;packages:PackageTree[]};
// Hash paths as well as bytes; reject links instead of following them outside the cache.
export function hashTree(root:string):{tree_sha256:string;files:number;bytes:number}{
const hash=createHash('sha256');let files=0,bytes=0;
function walk(path:string,relative:string){const st=lstatSync(path);requireThat(!st.isSymbolicLink(),'validator_cache_unsafe');if(st.isDirectory()){for(const name of readdirSync(path).sort())walk(join(path,name),relative?`${relative}/${name}`:name);}else{requireThat(st.isFile(),'validator_cache_unsafe');const data=readFileSync(path);hash.update(JSON.stringify([relative,data.length,createHash('sha256').update(data).digest('hex')])+'\n');files++;bytes+=data.length;}}
walk(root,'');return {tree_sha256:hash.digest('hex'),files,bytes};
}
export function snapshotDependencies(root:string):DependencyLock{
requireThat(lstatSync(root).isDirectory()&&!lstatSync(root).isSymbolicLink(),'validator_cache_unsafe');
const packages=readdirSync(root).filter(n=>n!=='packages.ini').sort().map(name=>{requireThat(/^[a-zA-Z0-9._-]+#[a-zA-Z0-9._+-]+$/.test(name),'validator_cache_unsafe');const path=join(root,name),tree=hashTree(path),meta=JSON.parse(readFileSync(join(path,'package/package.json'),'utf8'));requireThat(name===`${meta.name}#${meta.version}`,'validator_dependency_mismatch');return {name:meta.name,version:meta.version,...tree};});
requireThat(packages.length>0,'validator_dependencies_missing');const config=join(root,'packages.ini');if(existsSync(config))requireThat(lstatSync(config).isFile()&&!lstatSync(config).isSymbolicLink(),'validator_cache_unsafe');return {format:1,configuration_sha256:existsSync(config)?createHash('sha256').update(readFileSync(config)).digest('hex'):null,packages};
}
export function verifyDependencies(root:string,lock:DependencyLock):void{
try{requireThat(lock.format===1&&JSON.stringify(snapshotDependencies(root))===JSON.stringify(lock),'validator_dependency_mismatch');}catch(error){if(error&&typeof error==='object'&&'code' in error)throw error;requireThat(false,'validator_dependencies_missing');}
}