mod support;
use secure_exec_sidecar::wire::{
BootstrapRootFilesystemRequest, ConfigureVmRequest, DisposeReason, DisposeVmRequest,
GuestFilesystemCallRequest, GuestFilesystemOperation, GuestRuntimeKind, MountDescriptor,
MountPluginDescriptor, RequestPayload, ResponsePayload, RootFilesystemEntry,
RootFilesystemEntryEncoding, RootFilesystemEntryKind,
};
use std::collections::HashMap;
use std::fs;
use std::os::unix::fs::symlink;
use std::time::Duration;
use support::{
authenticate_wire, collect_process_output_wire_with_timeout, create_vm_wire, execute_wire,
new_sidecar, open_session_wire, temp_dir, wire_request, wire_vm,
};
#[test]
fn pnpm_symlinked_packages_resolve_from_guest_import() {
support::assert_node_available();
let mut sidecar = new_sidecar("node-modules-symlink");
let host_root = temp_dir("node-modules-symlink-host");
let node_modules = host_root.join("node_modules");
let store = node_modules.join(".pnpm");
let plain_real = store.join("is-number@7.0.0/node_modules/is-number");
fs::create_dir_all(&plain_real).expect("create plain store dir");
fs::write(
plain_real.join("package.json"),
r#"{"name":"is-number","version":"7.0.0","type":"module","main":"index.js"}"#,
)
.expect("write plain package.json");
fs::write(
plain_real.join("index.js"),
"export default () => \"plain-symlink-resolved\";\n",
)
.expect("write plain entry");
let scoped_real = store.join("@scope+pkg@1.0.0/node_modules/@scope/pkg");
fs::create_dir_all(&scoped_real).expect("create scoped store dir");
fs::write(
scoped_real.join("package.json"),
r#"{"name":"@scope/pkg","version":"1.0.0","type":"module","main":"index.js"}"#,
)
.expect("write scoped package.json");
fs::write(
scoped_real.join("index.js"),
"export default () => \"scoped-symlink-resolved\";\n",
)
.expect("write scoped entry");
symlink(
"./.pnpm/is-number@7.0.0/node_modules/is-number",
node_modules.join("is-number"),
)
.expect("link plain package");
fs::create_dir_all(node_modules.join("@scope")).expect("create @scope dir");
symlink(
"../.pnpm/@scope+pkg@1.0.0/node_modules/@scope/pkg",
node_modules.join("@scope/pkg"),
)
.expect("link scoped package");
let cwd = temp_dir("node-modules-symlink-cwd");
let connection_id = authenticate_wire(&mut sidecar, "conn-1");
let session_id = open_session_wire(&mut sidecar, 2, &connection_id);
let (vm_id, _) = create_vm_wire(
&mut sidecar,
3,
&connection_id,
&session_id,
GuestRuntimeKind::JavaScript,
&cwd,
);
sidecar
.dispatch_wire_blocking(wire_request(
4,
wire_vm(&connection_id, &session_id, &vm_id),
RequestPayload::BootstrapRootFilesystemRequest(BootstrapRootFilesystemRequest {
entries: vec![RootFilesystemEntry {
path: String::from("/tmp/node_modules"),
kind: RootFilesystemEntryKind::Directory,
mode: None,
uid: None,
gid: None,
content: None,
encoding: None,
target: None,
executable: false,
}],
}),
))
.expect("bootstrap mountpoint");
sidecar
.dispatch_wire_blocking(wire_request(
5,
wire_vm(&connection_id, &session_id, &vm_id),
RequestPayload::ConfigureVmRequest(ConfigureVmRequest {
mounts: vec![MountDescriptor {
guest_path: String::from("/tmp/node_modules"),
read_only: true,
plugin: MountPluginDescriptor {
id: String::from("host_dir"),
config: serde_json::to_string(&serde_json::json!({
"hostPath": node_modules.to_string_lossy(),
"readOnly": true,
}))
.expect("serialize host_dir mount config"),
},
}],
software: Vec::new(),
permissions: None,
module_access_cwd: None,
instructions: Vec::new(),
projected_modules: Vec::new(),
command_permissions: HashMap::new(),
loopback_exempt_ports: Vec::new(),
}),
))
.expect("mount host node_modules");
let entry_source = r#"
import plain from "is-number";
import scoped from "@scope/pkg";
console.log(plain());
console.log(scoped());
"#;
let write = sidecar
.dispatch_wire_blocking(wire_request(
6,
wire_vm(&connection_id, &session_id, &vm_id),
RequestPayload::GuestFilesystemCallRequest(GuestFilesystemCallRequest {
operation: GuestFilesystemOperation::WriteFile,
path: String::from("/tmp/entry.mjs"),
destination_path: None,
target: None,
content: Some(String::from(entry_source)),
encoding: Some(RootFilesystemEntryEncoding::Utf8),
recursive: false,
mode: None,
uid: None,
gid: None,
atime_ms: None,
mtime_ms: None,
len: None,
offset: None,
}),
))
.expect("write guest entry");
match write.response.payload {
ResponsePayload::GuestFilesystemResultResponse(_) => {}
other => panic!("unexpected guest write response: {other:?}"),
}
execute_wire(
&mut sidecar,
7,
&connection_id,
&session_id,
&vm_id,
"proc-resolve",
GuestRuntimeKind::JavaScript,
std::path::Path::new("/tmp/entry.mjs"),
Vec::new(),
);
let (stdout, stderr, exit) = collect_process_output_wire_with_timeout(
&mut sidecar,
&connection_id,
&session_id,
&vm_id,
"proc-resolve",
Duration::from_secs(10),
);
assert_eq!(
stdout.trim(),
"plain-symlink-resolved\nscoped-symlink-resolved",
"guest import of pnpm-symlinked packages should resolve; stderr: {stderr}"
);
assert_eq!(exit, 0, "stderr: {stderr}");
sidecar
.dispatch_wire_blocking(wire_request(
8,
wire_vm(&connection_id, &session_id, &vm_id),
RequestPayload::DisposeVmRequest(DisposeVmRequest {
reason: DisposeReason::Requested,
}),
))
.expect("dispose vm");
}