196 lines
6.9 KiB
Rust
196 lines
6.9 KiB
Rust
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<String, Vec<u8>>) -> Vec<u8> {
|
|
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<String, Vec<u8>>,
|
|
pub cache: RefCell<BTreeMap<String, Vec<u8>>>,
|
|
pub reads: RefCell<Vec<(String, usize)>>,
|
|
pub calls: RefCell<Vec<String>>,
|
|
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<String, Vec<u8>>) {
|
|
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::<Spec>())
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
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<BTreeMap<String, Vec<u8>>, 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<Vec<u8>, 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<Option<Vec<u8>>, 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<String, Vec<u8>>) -> 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::<String>()
|
|
};
|
|
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()
|
|
}
|