use super::super::super::Directory; use base64::{Engine, engine::general_purpose::STANDARD}; use dever::compiler::{ project::dependency::{ Lock, Store, library::{self, Spec, registry::Transport, runtime}, worker::signed::SignedResources, }, toolchain::BuildTarget, }; use ring::signature::{Ed25519KeyPair, KeyPair}; use serde_json::{Value, json}; use sha2::{Digest, Sha256, Sha512}; use std::{cell::RefCell, collections::BTreeMap, io::Cursor}; pub(super) fn hash(bytes: &[u8]) -> String { format!("{:x}", Sha256::digest(bytes)) } pub(super) fn archive(files: &BTreeMap>) -> Vec { let gzip = flate2::GzBuilder::new() .mtime(0) .write(Vec::new(), flate2::Compression::fast()); let mut builder = tar::Builder::new(gzip); for (path, bytes) in files { let mut header = tar::Header::new_gnu(); header.set_mode(if bytes.starts_with(b"#!") { 0o755 } else { 0o644 }); header.set_size(bytes.len() as u64); header.set_cksum(); builder .append_data(&mut header, path, Cursor::new(bytes)) .unwrap(); } builder.into_inner().unwrap().finish().unwrap() } pub(super) struct Fixture { pub directory: Directory, pub documents: BTreeMap>, pub cache: RefCell>>, pub reads: RefCell>, pub calls: RefCell>, pub metadata: runtime::Metadata, pub target: BuildTarget, pub signed: SignedResources, pub lock: Lock, } impl Fixture { pub fn new() -> Self { let directory = Directory::new(); let target = BuildTarget::host().unwrap(); let metadata = json!({"pack":{"name":"node","version":"22.1.0","sha256":hash(b"node pack")},"target":target.name(),"python_markers":null,"python_wheel_tags":[],"npm_libc":"glibc"}); let signed = signed( &directory, &BTreeMap::from([( format!("runtime/npm/{}/manifest.json", target.name()), serde_json::to_vec( &json!({"format":"dever-registry-runtime-v1","runtime":metadata}), ) .unwrap(), )]), ); Self { directory, documents: BTreeMap::new(), cache: RefCell::new(BTreeMap::new()), reads: RefCell::new(vec![]), calls: RefCell::new(vec![]), metadata: serde_json::from_value(metadata).unwrap(), target, signed, lock: Lock::new(vec![]), } } pub fn package(&mut self, document: Value, extra: BTreeMap>) { let name = document["name"].as_str().unwrap(); let version = document["version"].as_str().unwrap(); let base = format!("https://registry.npmjs.org/{name}"); let locator = format!("{base}/-/archive-{version}.tgz"); let mut files = extra; files.insert( "package/package.json".into(), serde_json::to_vec(&document).unwrap(), ); files.insert("package/index.js".into(), b"module.exports = 42;".to_vec()); let bytes = archive(&files); let mut metadata = document.clone(); metadata["dist"] = json!({"tarball":locator,"integrity":format!("sha512-{}",STANDARD.encode(Sha512::digest(&bytes)))}); let mut index: Value = self .documents .get(&base) .map(|bytes| serde_json::from_slice(bytes).unwrap()) .unwrap_or_else(|| json!({"name":name,"versions":{}})); index["versions"][version] = metadata.clone(); self.documents .insert(base.clone(), serde_json::to_vec(&index).unwrap()); self.documents.insert( format!("{base}/{version}"), serde_json::to_vec(&metadata).unwrap(), ); self.documents.insert(locator, bytes); } pub fn resolve(&mut self, roots: &[&str]) -> Result<(), String> { let specs = roots .iter() .map(|value| value.parse::()) .collect::, _>>()?; let mut lock = self.lock.clone(); library::npm::resolve(&specs, self.target, &self.metadata, self, self, &mut lock)?; self.lock = lock; Ok(()) } pub fn graph(&self) -> library::npm::Environment { serde_json::from_value(self.lock.npm[0].clone()).unwrap() } pub fn install(&self) -> Result>, String> { library::npm::install( &self.lock, &self.graph().roots, self.target, self, &self.signed, ) } } impl Transport for Fixture { fn get(&self, url: &str, limit: usize) -> Result, String> { library::registry::official(url)?; self.calls.borrow_mut().push(url.into()); let bytes = self .documents .get(url) .ok_or_else(|| "registry returned HTTP 404".to_owned())?; assert!(bytes.len() <= limit); Ok(bytes.clone()) } } impl Store for Fixture { fn get(&self, digest: &str, _: u64) -> Result>, String> { let bytes = self.cache.borrow().get(digest).cloned(); self.reads .borrow_mut() .push((digest.into(), bytes.as_ref().map_or(0, Vec::len))); Ok(bytes) } fn put(&self, bytes: &[u8]) -> Result<(), String> { self.cache.borrow_mut().insert(hash(bytes), bytes.to_vec()); Ok(()) } } pub(super) fn signed(directory: &Directory, files: &BTreeMap>) -> SignedResources { let root = directory.0.join("signed"); let key = Ed25519KeyPair::from_seed_unchecked(&[72; 32]).unwrap(); let hex = |bytes: &[u8]| { bytes .iter() .map(|byte| format!("{byte:02x}")) .collect::() }; let write = |path: &str, bytes: &[u8]| { let path = root.join(path); std::fs::create_dir_all(path.parent().unwrap()).unwrap(); std::fs::write(path, bytes).unwrap(); }; write( "state/trusted-release-key", hex(key.public_key().as_ref()).as_bytes(), ); let mut artifacts = vec![json!({"path":"base.txt","bytes":4,"sha256":hash(b"base")})]; let target = BuildTarget::host().unwrap(); for (path, bytes) in files { artifacts.push(json!({"path":path,"bytes":bytes.len(),"sha256":hash(bytes)})); write(&format!("versions/0.0.1/{path}"), bytes); } write("versions/0.0.1/base.txt", b"base"); let catalog=serde_json::to_vec(&json!({"format":"dever-release-v2","version":"0.0.1","platform":target.name(),"artifacts":artifacts,"extensions":[]})).unwrap(); write("versions/0.0.1/manifest.json", &catalog); write( "versions/0.0.1/manifest.sig", hex(key.sign(&catalog).as_ref()).as_bytes(), ); SignedResources::load(&root, "0.0.1").unwrap() }