feat: publish Dever core with project scaffolding and versioned AI skills
This commit is contained in:
1 parent
70fa73cdc2
commit
ac458ffa7f
960 files changed
+221319
-1163
No files matched your search
@@ -0,0 +1,691 @@
|
||||
use super::*;
|
||||
use dever_cli::libs::{LibResolver, LockedWorker, npm, resolve_workers};
|
||||
|
||||
pub(super) fn archive(
|
||||
name: &str,
|
||||
version: &str,
|
||||
mut manifest: serde_json::Value,
|
||||
extra: &[(&str, Vec<u8>)],
|
||||
) -> Vec<u8> {
|
||||
manifest["name"] = name.into();
|
||||
manifest["version"] = version.into();
|
||||
let encoder = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default());
|
||||
let mut writer = tar::Builder::new(encoder);
|
||||
for (name, bytes) in [
|
||||
(
|
||||
"package/package.json",
|
||||
serde_json::to_vec(&manifest).unwrap(),
|
||||
),
|
||||
(
|
||||
"package/index.js",
|
||||
format!("module.exports = '{name}/{version}';\n").into_bytes(),
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.chain(extra.iter().map(|(name, bytes)| (*name, bytes.clone())))
|
||||
{
|
||||
let mut header = tar::Header::new_gnu();
|
||||
header.set_size(bytes.len() as u64);
|
||||
header.set_mode(0o644);
|
||||
header.set_cksum();
|
||||
writer
|
||||
.append_data(&mut header, name, bytes.as_slice())
|
||||
.unwrap();
|
||||
}
|
||||
writer.into_inner().unwrap().finish().unwrap()
|
||||
}
|
||||
|
||||
pub(super) fn release(
|
||||
registry: &mut LocalRegistry,
|
||||
name: &str,
|
||||
version: &str,
|
||||
manifest: serde_json::Value,
|
||||
extra: &[(&str, Vec<u8>)],
|
||||
) {
|
||||
let archive = archive(name, version, manifest.clone(), extra);
|
||||
let index_path = (Ecosystem::Npm, format!("/{name}"));
|
||||
let mut index = registry
|
||||
.0
|
||||
.get(&index_path)
|
||||
.map(|bytes| serde_json::from_slice::<serde_json::Value>(bytes).unwrap())
|
||||
.unwrap_or(serde_json::json!({"versions":{}}));
|
||||
index["versions"][version] = serde_json::json!({});
|
||||
registry
|
||||
.0
|
||||
.insert(index_path, serde_json::to_vec(&index).unwrap());
|
||||
let mut document = manifest;
|
||||
document["name"] = name.into();
|
||||
document["version"] = version.into();
|
||||
let url = format!("https://mock/{name}/{version}.tgz");
|
||||
document["dist"] = serde_json::json!({"tarball":url,"integrity":format!("sha512-{}", base64::engine::general_purpose::STANDARD.encode(Sha512::digest(&archive)))});
|
||||
registry.0.insert(
|
||||
(Ecosystem::Npm, format!("/{name}/{version}")),
|
||||
serde_json::to_vec(&document).unwrap(),
|
||||
);
|
||||
registry.0.insert((Ecosystem::Npm, url), archive);
|
||||
}
|
||||
|
||||
fn resolved(registry: &LocalRegistry, requested: &[&str]) -> (LockFile, FixtureArtifactStore) {
|
||||
let store = FixtureArtifactStore::default();
|
||||
let lock = local_resolver(registry, &store)
|
||||
.resolve(
|
||||
&requested
|
||||
.iter()
|
||||
.map(|spec| spec.parse().unwrap())
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.unwrap();
|
||||
doctor(&lock).unwrap();
|
||||
assert_eq!(LockFile::decode(&lock.encode().unwrap()).unwrap(), lock);
|
||||
(lock, store)
|
||||
}
|
||||
|
||||
fn files(lock: &LockFile, store: &FixtureArtifactStore, index: usize) -> BTreeMap<String, Vec<u8>> {
|
||||
let archives = lock
|
||||
.libs
|
||||
.iter()
|
||||
.map(|lib| {
|
||||
(
|
||||
lib.spec.clone(),
|
||||
store
|
||||
.verify_exact(
|
||||
&lib.artifacts[0].sha256,
|
||||
lib.artifacts[0].bytes,
|
||||
&lib.artifacts[0].target,
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
})
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
npm::files(&lock.npm[index], lock, |lib| {
|
||||
Ok(archives[&lib.spec].as_slice())
|
||||
})
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_versions_and_shared_packages_keep_distinct_peer_contexts() {
|
||||
let mut registry = LocalRegistry(BTreeMap::new());
|
||||
for (name, dependencies) in [
|
||||
("left", serde_json::json!({"plugin":"1.0.0","host":"1.0.0"})),
|
||||
(
|
||||
"right",
|
||||
serde_json::json!({"plugin":"1.0.0","host":"2.0.0"}),
|
||||
),
|
||||
] {
|
||||
release(
|
||||
&mut registry,
|
||||
name,
|
||||
"1.0.0",
|
||||
serde_json::json!({"dependencies":dependencies}),
|
||||
&[],
|
||||
);
|
||||
}
|
||||
release(
|
||||
&mut registry,
|
||||
"plugin",
|
||||
"1.0.0",
|
||||
serde_json::json!({"peerDependencies":{"host":">=1.0.0 <3.0.0"}}),
|
||||
&[],
|
||||
);
|
||||
for version in ["1.0.0", "2.0.0"] {
|
||||
release(&mut registry, "host", version, serde_json::json!({}), &[]);
|
||||
}
|
||||
let (lock, store) = resolved(®istry, &["npm:left@1.0.0", "npm:right@1.0.0"]);
|
||||
assert_eq!(lock.libs.len(), 5);
|
||||
let files = files(&lock, &store, 0);
|
||||
for (side, version) in [("left", "1.0.0"), ("right", "2.0.0")] {
|
||||
let node = lock.npm[0]
|
||||
.instances
|
||||
.iter()
|
||||
.find(|node| node.path == format!("node_modules/{side}/node_modules/plugin"))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
node.edges[0].target.as_deref(),
|
||||
Some(format!("node_modules/{side}/node_modules/host").as_str())
|
||||
);
|
||||
assert!(
|
||||
String::from_utf8(
|
||||
files[&format!("node_modules/{side}/node_modules/host/index.js")].clone()
|
||||
)
|
||||
.unwrap()
|
||||
.contains(version)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn optional_dependencies_override_regular_edges_and_record_target_or_absence() {
|
||||
let mut registry = LocalRegistry(BTreeMap::new());
|
||||
release(
|
||||
&mut registry,
|
||||
"root",
|
||||
"1.0.0",
|
||||
serde_json::json!({"dependencies":{"child":"1.0.0"},"optionalDependencies":{"child":"2.0.0","missing":"*","darwin":"*","musl":"*"}}),
|
||||
&[],
|
||||
);
|
||||
release(&mut registry, "child", "2.0.0", serde_json::json!({}), &[]);
|
||||
release(
|
||||
&mut registry,
|
||||
"darwin",
|
||||
"1.0.0",
|
||||
serde_json::json!({"os":["darwin"]}),
|
||||
&[],
|
||||
);
|
||||
release(
|
||||
&mut registry,
|
||||
"musl",
|
||||
"1.0.0",
|
||||
serde_json::json!({"os":["linux"],"libc":["musl"]}),
|
||||
&[],
|
||||
);
|
||||
let (lock, store) = resolved(®istry, &["npm:root@1.0.0"]);
|
||||
assert_eq!(lock.libs.len(), 2);
|
||||
let root = lock.npm[0]
|
||||
.instances
|
||||
.iter()
|
||||
.find(|node| node.spec.name == "root")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
root.edges
|
||||
.iter()
|
||||
.find(|edge| edge.name == "child")
|
||||
.unwrap()
|
||||
.kind,
|
||||
npm::Kind::Optional
|
||||
);
|
||||
for name in ["darwin", "musl"] {
|
||||
assert_eq!(
|
||||
root.edges
|
||||
.iter()
|
||||
.find(|edge| edge.name == name)
|
||||
.unwrap()
|
||||
.omission,
|
||||
Some(npm::Omission::Target)
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
root.edges
|
||||
.iter()
|
||||
.find(|edge| edge.name == "missing")
|
||||
.unwrap()
|
||||
.omission,
|
||||
Some(npm::Omission::Unavailable)
|
||||
);
|
||||
assert!(
|
||||
!files(&lock, &store, 0)
|
||||
.keys()
|
||||
.any(|path| path.contains("darwin") || path.contains("musl"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn optional_integrity_and_protocol_failures_are_never_omissions() {
|
||||
let mut registry = LocalRegistry(BTreeMap::new());
|
||||
release(
|
||||
&mut registry,
|
||||
"root",
|
||||
"1.0.0",
|
||||
serde_json::json!({"optionalDependencies":{"child":"*"}}),
|
||||
&[],
|
||||
);
|
||||
release(&mut registry, "child", "1.0.0", serde_json::json!({}), &[]);
|
||||
registry.0.insert(
|
||||
(Ecosystem::Npm, "https://mock/child/1.0.0.tgz".into()),
|
||||
b"tampered".to_vec(),
|
||||
);
|
||||
let store = FixtureArtifactStore::default();
|
||||
assert!(
|
||||
local_resolver(®istry, &store)
|
||||
.resolve(&["npm:root@1.0.0".parse().unwrap()])
|
||||
.unwrap_err()
|
||||
.contains("integrity mismatch")
|
||||
);
|
||||
release(
|
||||
&mut registry,
|
||||
"child",
|
||||
"1.0.0",
|
||||
serde_json::json!({"dependencies":{"other":"not-a-semver"}}),
|
||||
&[],
|
||||
);
|
||||
assert!(
|
||||
local_resolver(®istry, &store)
|
||||
.resolve(&["npm:root@1.0.0".parse().unwrap()])
|
||||
.unwrap_err()
|
||||
.contains("invalid npm range")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn required_peers_auto_install_and_backtrack_but_optional_peers_do_not() {
|
||||
let mut registry = LocalRegistry(BTreeMap::new());
|
||||
release(
|
||||
&mut registry,
|
||||
"root",
|
||||
"1.0.0",
|
||||
serde_json::json!({"dependencies":{"plugin":"*","host":"1.0.0"}}),
|
||||
&[],
|
||||
);
|
||||
release(
|
||||
&mut registry,
|
||||
"plugin",
|
||||
"2.0.0",
|
||||
serde_json::json!({"peerDependencies":{"host":"2.0.0"}}),
|
||||
&[],
|
||||
);
|
||||
release(
|
||||
&mut registry,
|
||||
"plugin",
|
||||
"1.0.0",
|
||||
serde_json::json!({"peerDependencies":{"host":"1.0.0","absent":"*"},"peerDependenciesMeta":{"absent":{"optional":true}}}),
|
||||
&[],
|
||||
);
|
||||
for version in ["1.0.0", "2.0.0"] {
|
||||
release(&mut registry, "host", version, serde_json::json!({}), &[]);
|
||||
}
|
||||
let (lock, _) = resolved(®istry, &["npm:root@1.0.0"]);
|
||||
let plugin = lock.npm[0]
|
||||
.instances
|
||||
.iter()
|
||||
.find(|node| node.spec.name == "plugin")
|
||||
.unwrap();
|
||||
assert_eq!(plugin.spec.version, "1.0.0");
|
||||
assert_eq!(
|
||||
plugin
|
||||
.edges
|
||||
.iter()
|
||||
.find(|edge| edge.name == "absent")
|
||||
.unwrap()
|
||||
.omission,
|
||||
Some(npm::Omission::OptionalPeerAbsent)
|
||||
);
|
||||
let (lock, _) = resolved(®istry, &["npm:plugin@1.0.0"]);
|
||||
assert!(
|
||||
lock.npm[0]
|
||||
.instances
|
||||
.iter()
|
||||
.any(|node| node.path == "node_modules/host")
|
||||
);
|
||||
let store = FixtureArtifactStore::default();
|
||||
assert!(
|
||||
local_resolver(®istry, &store)
|
||||
.resolve(&[
|
||||
"npm:plugin@2.0.0".parse().unwrap(),
|
||||
"npm:host@1.0.0".parse().unwrap()
|
||||
])
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bundled_transitive_packages_are_bound_to_parent_and_never_downloaded() {
|
||||
let mut registry = LocalRegistry(BTreeMap::new());
|
||||
let extra = vec![
|
||||
(
|
||||
"package/node_modules/b/package.json",
|
||||
br#"{"name":"b","version":"1.0.0","dependencies":{"c":"2.0.0"}}"#.to_vec(),
|
||||
),
|
||||
(
|
||||
"package/node_modules/b/index.js",
|
||||
b"module.exports = require('c');".to_vec(),
|
||||
),
|
||||
(
|
||||
"package/node_modules/c/package.json",
|
||||
br#"{"name":"c","version":"2.0.0"}"#.to_vec(),
|
||||
),
|
||||
(
|
||||
"package/node_modules/c/index.js",
|
||||
b"module.exports = 42;".to_vec(),
|
||||
),
|
||||
];
|
||||
release(
|
||||
&mut registry,
|
||||
"root",
|
||||
"1.0.0",
|
||||
serde_json::json!({"dependencies":{"b":"1.0.0"},"bundleDependencies":["b"]}),
|
||||
&extra,
|
||||
);
|
||||
let (lock, store) = resolved(®istry, &["npm:root@1.0.0"]);
|
||||
assert_eq!(lock.libs.len(), 1);
|
||||
assert_eq!(lock.npm[0].instances.len(), 3);
|
||||
assert_eq!(
|
||||
files(&lock, &store, 0)["node_modules/root/node_modules/c/index.js"],
|
||||
b"module.exports = 42;"
|
||||
);
|
||||
release(
|
||||
&mut registry,
|
||||
"root",
|
||||
"1.0.0",
|
||||
serde_json::json!({}),
|
||||
&extra,
|
||||
);
|
||||
assert!(
|
||||
local_resolver(®istry, &store)
|
||||
.resolve(&["npm:root@1.0.0".parse().unwrap()])
|
||||
.unwrap_err()
|
||||
.contains("undeclared")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn worker_root_sets_keep_separate_npm_environments_and_detect_lock_tampering() {
|
||||
let mut registry = LocalRegistry(BTreeMap::new());
|
||||
release(
|
||||
&mut registry,
|
||||
"plugin",
|
||||
"1.0.0",
|
||||
serde_json::json!({"peerDependencies":{"host":"*"}}),
|
||||
&[],
|
||||
);
|
||||
for version in ["1.0.0", "2.0.0"] {
|
||||
release(&mut registry, "host", version, serde_json::json!({}), &[]);
|
||||
}
|
||||
let store = FixtureArtifactStore::default();
|
||||
let worker = |adapter: &str, version: &str| LockedWorker {
|
||||
port: "sample.worker".into(),
|
||||
adapter: adapter.into(),
|
||||
ecosystem: "npm".into(),
|
||||
runtime: Some(registry_runtime(Ecosystem::Npm).pack),
|
||||
entry: format!("worker/{adapter}.mjs"),
|
||||
schema: "checked-port".into(),
|
||||
capabilities: vec![],
|
||||
operations: vec!["read".into()],
|
||||
libs: vec![
|
||||
"npm:plugin@1.0.0".parse().unwrap(),
|
||||
format!("npm:host@{version}").parse().unwrap(),
|
||||
],
|
||||
};
|
||||
let lock = resolve_workers(
|
||||
&local_resolver(®istry, &store),
|
||||
&[],
|
||||
vec![worker("first", "1.0.0"), worker("second", "2.0.0")],
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(lock.npm.len(), 2);
|
||||
for index in 0..2 {
|
||||
assert!(files(&lock, &store, index).contains_key("node_modules/plugin/index.js"));
|
||||
}
|
||||
let mut changed = lock.clone();
|
||||
changed.npm[0].instances[0].path = "../escape".into();
|
||||
assert!(doctor(&changed).is_err());
|
||||
let mut changed = lock.clone();
|
||||
changed.npm[0]
|
||||
.instances
|
||||
.iter_mut()
|
||||
.find(|node| node.spec.name == "plugin")
|
||||
.unwrap()
|
||||
.edges[0]
|
||||
.target = Some("node_modules/missing".into());
|
||||
assert!(doctor(&changed).is_err());
|
||||
let mut changed = lock;
|
||||
changed.npm[0]
|
||||
.instances
|
||||
.iter_mut()
|
||||
.find(|node| node.spec.name == "plugin")
|
||||
.unwrap()
|
||||
.edges
|
||||
.clear();
|
||||
let archives = changed
|
||||
.libs
|
||||
.iter()
|
||||
.map(|lib| {
|
||||
(
|
||||
lib.spec.clone(),
|
||||
store
|
||||
.verify_exact(
|
||||
&lib.artifacts[0].sha256,
|
||||
lib.artifacts[0].bytes,
|
||||
&lib.artifacts[0].target,
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
})
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
assert!(
|
||||
npm::files(&changed.npm[0], &changed, |lib| Ok(
|
||||
archives[&lib.spec].as_slice()
|
||||
))
|
||||
.unwrap_err()
|
||||
.contains("differs from package.json")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bundled_instances_cannot_switch_to_another_parents_same_version_package() {
|
||||
let mut registry = LocalRegistry(BTreeMap::new());
|
||||
for name in ["a", "c"] {
|
||||
release(
|
||||
&mut registry,
|
||||
name,
|
||||
"1.0.0",
|
||||
serde_json::json!({"dependencies":{"b":"1.0.0"},"bundleDependencies":["b"]}),
|
||||
&[
|
||||
(
|
||||
"package/node_modules/b/package.json",
|
||||
br#"{"name":"b","version":"1.0.0"}"#.to_vec(),
|
||||
),
|
||||
(
|
||||
"package/node_modules/b/index.js",
|
||||
format!("module.exports = '{name}';").into_bytes(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
release(&mut registry, "b", "1.0.0", serde_json::json!({}), &[]);
|
||||
let (original, store) = resolved(®istry, &["npm:a@1.0.0", "npm:c@1.0.0", "npm:b@1.0.0"]);
|
||||
let archives = original
|
||||
.libs
|
||||
.iter()
|
||||
.map(|lib| {
|
||||
(
|
||||
lib.spec.clone(),
|
||||
store
|
||||
.verify_exact(
|
||||
&lib.artifacts[0].sha256,
|
||||
lib.artifacts[0].bytes,
|
||||
&lib.artifacts[0].target,
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
})
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
for (archive, prefix) in [
|
||||
("npm:c@1.0.0", "package/node_modules/b/"),
|
||||
("npm:b@1.0.0", "package/"),
|
||||
] {
|
||||
let mut changed = original.clone();
|
||||
changed.npm[0]
|
||||
.instances
|
||||
.iter_mut()
|
||||
.find(|node| node.path == "node_modules/a/node_modules/b")
|
||||
.unwrap()
|
||||
.source = npm::Source {
|
||||
archive: archive.parse().unwrap(),
|
||||
prefix: prefix.into(),
|
||||
};
|
||||
assert!(
|
||||
doctor(&changed)
|
||||
.unwrap_err()
|
||||
.contains("parent archive installation")
|
||||
);
|
||||
assert!(
|
||||
npm::files(&changed.npm[0], &changed, |lib| Ok(
|
||||
archives[&lib.spec].as_slice()
|
||||
))
|
||||
.unwrap_err()
|
||||
.contains("parent archive installation")
|
||||
);
|
||||
// Removing the locked layout cannot hide the original archive binding.
|
||||
for node in &mut changed.npm[0].instances {
|
||||
node.bundled.clear();
|
||||
}
|
||||
assert!(
|
||||
npm::files(&changed.npm[0], &changed, |lib| Ok(
|
||||
archives[&lib.spec].as_slice()
|
||||
))
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn production_and_optional_dependencies_override_same_named_peers() {
|
||||
let mut registry = LocalRegistry(BTreeMap::new());
|
||||
release(&mut registry, "host", "2.0.0", serde_json::json!({}), &[]);
|
||||
release(
|
||||
&mut registry,
|
||||
"plugin",
|
||||
"1.0.0",
|
||||
serde_json::json!({"dependencies":{"host":"2.0.0"},"peerDependencies":{"host":"1.0.0"}}),
|
||||
&[],
|
||||
);
|
||||
let (lock, _) = resolved(®istry, &["npm:plugin@1.0.0", "npm:host@2.0.0"]);
|
||||
let plugin = lock.npm[0]
|
||||
.instances
|
||||
.iter()
|
||||
.find(|node| node.spec.name == "plugin")
|
||||
.unwrap();
|
||||
assert_eq!(plugin.edges.len(), 1);
|
||||
assert_eq!(plugin.edges[0].kind, npm::Kind::Dependency);
|
||||
assert_eq!(
|
||||
plugin.edges[0].target.as_deref(),
|
||||
Some("node_modules/plugin/node_modules/host")
|
||||
);
|
||||
release(
|
||||
&mut registry,
|
||||
"plugin",
|
||||
"1.0.0",
|
||||
serde_json::json!({"optionalDependencies":{"absent":"*"},"peerDependencies":{"absent":"1.0.0"}}),
|
||||
&[],
|
||||
);
|
||||
let (lock, _) = resolved(®istry, &["npm:plugin@1.0.0"]);
|
||||
assert_eq!(lock.npm[0].instances[0].edges.len(), 1);
|
||||
assert_eq!(lock.npm[0].instances[0].edges[0].kind, npm::Kind::Optional);
|
||||
assert_eq!(
|
||||
lock.npm[0].instances[0].edges[0].omission,
|
||||
Some(npm::Omission::Unavailable)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removing_a_root_prunes_its_entire_dependency_cycle() {
|
||||
for bundled in [false, true] {
|
||||
let mut registry = LocalRegistry(BTreeMap::new());
|
||||
release(&mut registry, "z", "1.0.0", serde_json::json!({}), &[]);
|
||||
let b = serde_json::json!({"name":"b","version":"1.0.0","dependencies":{"a":"1.0.0"}});
|
||||
let mut a = serde_json::json!({"dependencies":{"b":"1.0.0"}});
|
||||
let extra = if bundled {
|
||||
a["bundleDependencies"] = serde_json::json!(["b"]);
|
||||
vec![(
|
||||
"package/node_modules/b/package.json",
|
||||
serde_json::to_vec(&b).unwrap(),
|
||||
)]
|
||||
} else {
|
||||
release(&mut registry, "b", "1.0.0", b, &[]);
|
||||
vec![]
|
||||
};
|
||||
release(&mut registry, "a", "1.0.0", a, &extra);
|
||||
let (lock, _) = resolved(®istry, &["npm:a@1.0.0", "npm:z@1.0.0"]);
|
||||
let root = TemporaryDirectory::new();
|
||||
fs::create_dir(root.path().join("config")).unwrap();
|
||||
fs::write(
|
||||
root.path().join("config/setting.json"),
|
||||
br#"{"lib":["npm:a@1.0.0","npm:z@1.0.0"]}"#,
|
||||
)
|
||||
.unwrap();
|
||||
lock.write_atomic(root.path()).unwrap();
|
||||
dever_cli::libs::execute("remove", root.path(), &["npm:a@1.0.0".into()]).unwrap();
|
||||
let retained =
|
||||
LockFile::decode(&fs::read(root.path().join("dever.lock")).unwrap()).unwrap();
|
||||
assert_eq!(retained.libs.len(), 1);
|
||||
assert_eq!(retained.libs[0].spec.name, "z");
|
||||
assert_eq!(retained.npm[0].instances.len(), 1);
|
||||
doctor(&retained).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removing_a_dependency_still_required_by_a_remaining_root_is_rejected() {
|
||||
let mut registry = LocalRegistry(BTreeMap::new());
|
||||
release(
|
||||
&mut registry,
|
||||
"a",
|
||||
"1.0.0",
|
||||
serde_json::json!({"dependencies":{"b":"1.0.0"}}),
|
||||
&[],
|
||||
);
|
||||
release(&mut registry, "b", "1.0.0", serde_json::json!({}), &[]);
|
||||
let (lock, _) = resolved(®istry, &["npm:a@1.0.0", "npm:b@1.0.0"]);
|
||||
let root = TemporaryDirectory::new();
|
||||
fs::create_dir(root.path().join("config")).unwrap();
|
||||
let setting = br#"{"lib":["npm:a@1.0.0","npm:b@1.0.0"]}"#;
|
||||
fs::write(root.path().join("config/setting.json"), setting).unwrap();
|
||||
lock.write_atomic(root.path()).unwrap();
|
||||
let before = fs::read(root.path().join("dever.lock")).unwrap();
|
||||
assert!(
|
||||
dever_cli::libs::execute("remove", root.path(), &["npm:b@1.0.0".into()])
|
||||
.unwrap_err()
|
||||
.contains("required by")
|
||||
);
|
||||
assert_eq!(fs::read(root.path().join("dever.lock")).unwrap(), before);
|
||||
assert_eq!(
|
||||
fs::read(root.path().join("config/setting.json")).unwrap(),
|
||||
setting
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repeated_archive_instances_are_bounded_before_materialization_clones() {
|
||||
let mut registry = LocalRegistry(BTreeMap::new());
|
||||
release(
|
||||
&mut registry,
|
||||
"leaf",
|
||||
"1.0.0",
|
||||
serde_json::json!({}),
|
||||
&[("package/payload.js", vec![b'x'; 2 * 1024 * 1024])],
|
||||
);
|
||||
let mut roots = Vec::new();
|
||||
for number in 0..129 {
|
||||
let name = format!("root-{number}");
|
||||
release(
|
||||
&mut registry,
|
||||
&name,
|
||||
"1.0.0",
|
||||
serde_json::json!({"dependencies":{"leaf":"1.0.0"}}),
|
||||
&[],
|
||||
);
|
||||
roots.push(format!("npm:{name}@1.0.0"));
|
||||
}
|
||||
let (lock, store) = resolved(
|
||||
®istry,
|
||||
&roots.iter().map(String::as_str).collect::<Vec<_>>(),
|
||||
);
|
||||
assert_eq!(lock.npm[0].instances.len(), 258);
|
||||
let archives = lock
|
||||
.libs
|
||||
.iter()
|
||||
.map(|lib| {
|
||||
(
|
||||
lib.spec.clone(),
|
||||
store
|
||||
.verify_exact(
|
||||
&lib.artifacts[0].sha256,
|
||||
lib.artifacts[0].bytes,
|
||||
&lib.artifacts[0].target,
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
})
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
assert!(
|
||||
npm::files(
|
||||
&lock.npm[0],
|
||||
&lock,
|
||||
|lib| Ok(archives[&lib.spec].as_slice())
|
||||
)
|
||||
.unwrap_err()
|
||||
.contains("tree file/byte budget")
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user