Skip to content

Commit ab2d57f

Browse files
authored
feat: strip jsr meta module graph data on global to local copy (#78)
1 parent 1e389de commit ab2d57f

File tree

3 files changed

+136
-2
lines changed

3 files changed

+136
-2
lines changed

rs_lib/src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ pub mod wasm {
4646
use sys_traits::impls::wasm_path_to_str;
4747
use sys_traits::impls::wasm_string_to_path;
4848
use sys_traits::impls::RealSys;
49+
use sys_traits::EnvVar;
4950
use url::Url;
5051
use wasm_bindgen::prelude::*;
5152

@@ -131,6 +132,15 @@ pub mod wasm {
131132
console_error_panic_hook::set_once();
132133
let global =
133134
crate::GlobalHttpCache::new(RealSys, wasm_string_to_path(global_path));
135+
let jsr_url = RealSys
136+
.env_var("JSR_URL")
137+
.ok()
138+
.and_then(|url| {
139+
// ensure there is a trailing slash for the directory
140+
let registry_url = format!("{}/", url.trim_end_matches('/'));
141+
Url::parse(&registry_url).ok()
142+
})
143+
.unwrap_or_else(|| Url::parse("https://jsr.io/").unwrap());
134144
let local = crate::LocalHttpCache::new(
135145
wasm_string_to_path(local_path),
136146
new_rc(global),
@@ -139,6 +149,7 @@ pub mod wasm {
139149
} else {
140150
GlobalToLocalCopy::Disallow
141151
},
152+
jsr_url,
142153
);
143154
Self { cache: local }
144155
}

rs_lib/src/local.rs

Lines changed: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ impl<TSys: LocalHttpCacheSys> LocalLspHttpCache<TSys> {
103103
// 2. We need to verify the checksums for JSR https specifiers match what
104104
// is found in the package's manifest.
105105
allow_global_to_local: GlobalToLocalCopy::Disallow,
106+
jsr_registry_url: None, // only used when GlobalToLocalCopy::Allow
106107
},
107108
}
108109
}
@@ -250,13 +251,15 @@ pub struct LocalHttpCache<TSys: LocalHttpCacheSys> {
250251
manifest: LocalCacheManifest<TSys>,
251252
global_cache: GlobalHttpCacheRc<TSys>,
252253
allow_global_to_local: GlobalToLocalCopy,
254+
jsr_registry_url: Option<Url>,
253255
}
254256

255257
impl<TSys: LocalHttpCacheSys> LocalHttpCache<TSys> {
256258
pub fn new(
257259
path: PathBuf,
258260
global_cache: GlobalHttpCacheRc<TSys>,
259261
allow_global_to_local: GlobalToLocalCopy,
262+
jsr_registry_url: Url,
260263
) -> Self {
261264
#[cfg(not(feature = "wasm"))]
262265
assert!(path.is_absolute());
@@ -269,6 +272,7 @@ impl<TSys: LocalHttpCacheSys> LocalHttpCache<TSys> {
269272
manifest,
270273
global_cache,
271274
allow_global_to_local,
275+
jsr_registry_url: Some(jsr_registry_url),
272276
}
273277
}
274278

@@ -332,6 +336,54 @@ impl<TSys: LocalHttpCacheSys> LocalHttpCache<TSys> {
332336
Ok(None)
333337
}
334338
}
339+
340+
fn transform_content_on_copy_to_local(
341+
&self,
342+
url: &Url,
343+
content: Cow<'static, [u8]>,
344+
) -> Cow<'static, [u8]> {
345+
let Some(jsr_url) = &self.jsr_registry_url else {
346+
return content;
347+
};
348+
if is_jsr_version_metadata_url(url, jsr_url) {
349+
if let Some(data) = transform_jsr_version_metadata(&content) {
350+
return Cow::Owned(data);
351+
}
352+
}
353+
content
354+
}
355+
}
356+
357+
fn is_jsr_version_metadata_url(url: &Url, jsr_url: &Url) -> bool {
358+
// example url: https://jsr.io/@david/dax/0.43.0_meta.json
359+
let Some(suffix) = url.as_str().strip_prefix(jsr_url.as_str()) else {
360+
return false;
361+
};
362+
let Some(suffix) = suffix.strip_prefix('@') else {
363+
return false;
364+
};
365+
let Some(prefix) = suffix.strip_suffix("_meta.json") else {
366+
return false;
367+
};
368+
prefix.chars().filter(|c| *c == '/').count() == 2
369+
}
370+
371+
fn transform_jsr_version_metadata(content: &[u8]) -> Option<Vec<u8>> {
372+
let mut json_data =
373+
serde_json::from_slice::<serde_json::Value>(content).ok()?;
374+
let obj = json_data.as_object_mut()?;
375+
let keys_to_remove = obj
376+
.keys()
377+
.filter(|k| k.starts_with("moduleGraph"))
378+
.cloned()
379+
.collect::<Vec<_>>();
380+
if keys_to_remove.is_empty() {
381+
return None;
382+
}
383+
for key in keys_to_remove {
384+
obj.remove(&key);
385+
}
386+
serde_json::to_vec(&json_data).ok()
335387
}
336388

337389
impl<TSys: LocalHttpCacheSys> HttpCache for LocalHttpCache<TSys> {
@@ -434,13 +486,15 @@ impl<TSys: LocalHttpCacheSys> HttpCache for LocalHttpCache<TSys> {
434486
let maybe_global_cache_file =
435487
self.global_cache.get(&global_key, maybe_checksum)?;
436488
if let Some(file) = maybe_global_cache_file {
489+
let content = self
490+
.transform_content_on_copy_to_local(&key.url, file.content);
437491
atomic_write_file_with_retries(
438492
self.env(),
439493
&local_file_path,
440-
&file.content,
494+
&content,
441495
CACHE_PERM,
442496
)?;
443-
file.content
497+
content
444498
} else {
445499
return Ok(None);
446500
}
@@ -1054,6 +1108,7 @@ mod test {
10541108
local_temp.clone(),
10551109
global_cache.clone(),
10561110
GlobalToLocalCopy::Allow,
1111+
Url::parse("https://jsr.io/").unwrap(),
10571112
));
10581113
Self {
10591114
global_cache,
@@ -1216,4 +1271,60 @@ mod test {
12161271
Ok(None)
12171272
));
12181273
}
1274+
1275+
#[test]
1276+
fn test_copy_version_metadata_file() {
1277+
let test_caches = TestCaches::new();
1278+
let metadata_url =
1279+
Url::parse("https://jsr.io/@david/dax/1.2.3_meta.json").unwrap();
1280+
let data =
1281+
r#"{ "moduleGraph2": "testing", "checksums": { "test": "test" } }"#;
1282+
test_caches
1283+
.global_cache
1284+
.set(&metadata_url, Default::default(), data.as_bytes())
1285+
.unwrap();
1286+
let key = test_caches
1287+
.local_cache
1288+
.cache_item_key(&metadata_url)
1289+
.unwrap();
1290+
let final_data = test_caches.local_cache.get(&key, None).unwrap().unwrap();
1291+
assert_eq!(
1292+
String::from_utf8(final_data.content.to_vec()).unwrap(),
1293+
// had the moduleGraph2 property stripped
1294+
r#"{"checksums":{"test":"test"}}"#
1295+
);
1296+
}
1297+
1298+
#[test]
1299+
fn test_is_jsr_version_metadata_url() {
1300+
let cases = [
1301+
("https://jsr.io/@test/test/1.2.3_meta.json", true),
1302+
("https://jsr.io/@test/test/test/1.2.3_meta.json", false),
1303+
("https://jsr.io/@test/test/meta.json", false),
1304+
("https://jsr.io/test/test/1.2.3_meta.json", false),
1305+
("https://jsr.com/@test/test/1.2.3_meta.json", false),
1306+
];
1307+
let jsr_url = Url::parse("https://jsr.io/").unwrap();
1308+
for (url, expected) in cases {
1309+
let value =
1310+
is_jsr_version_metadata_url(&Url::parse(url).unwrap(), &jsr_url);
1311+
assert_eq!(value, expected);
1312+
}
1313+
}
1314+
1315+
#[test]
1316+
fn test_transform_jsr_version_metadata() {
1317+
let cases = [
1318+
(
1319+
r#"{ "moduleGraph1": "data", "moduleGraph2": "data", "moduleGraph3": "data", "other": "data" }"#,
1320+
Some(r#"{"other":"data"}"#),
1321+
),
1322+
(r#"{ "other": "data" }"#, None),
1323+
];
1324+
1325+
for (input, expected) in cases {
1326+
let output = transform_jsr_version_metadata(input.as_bytes());
1327+
assert_eq!(output.as_deref(), expected.map(|e| e.as_bytes()))
1328+
}
1329+
}
12191330
}

rs_lib/tests/integration_test.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ use sys_traits::impls::RealSys;
1818
use tempfile::TempDir;
1919
use url::Url;
2020

21+
fn jsr_url() -> Url {
22+
Url::parse("https://jsr.io/").unwrap()
23+
}
24+
2125
#[test]
2226
fn test_global_create_cache() {
2327
let dir = TempDir::new().unwrap();
@@ -110,6 +114,7 @@ fn test_local_global_cache() {
110114
local_cache_path.clone(),
111115
global_cache.clone(),
112116
GlobalToLocalCopy::Allow,
117+
jsr_url(),
113118
);
114119

115120
let manifest_file_path = local_cache_path.join("manifest.json");
@@ -256,6 +261,7 @@ fn test_local_global_cache() {
256261
local_cache_path.clone(),
257262
global_cache.clone(),
258263
GlobalToLocalCopy::Allow,
264+
jsr_url(),
259265
);
260266

261267
// now try caching a file with many headers
@@ -322,6 +328,7 @@ fn test_local_global_cache() {
322328
local_cache_path.to_path_buf(),
323329
global_cache.clone(),
324330
GlobalToLocalCopy::Allow,
331+
jsr_url(),
325332
));
326333
}
327334

@@ -331,6 +338,7 @@ fn test_local_global_cache() {
331338
local_cache_path.clone(),
332339
global_cache.clone(),
333340
GlobalToLocalCopy::Allow,
341+
jsr_url(),
334342
);
335343

336344
// try a file that can't be mapped to the file system
@@ -388,6 +396,7 @@ fn test_local_global_cache() {
388396
local_cache_path.to_path_buf(),
389397
global_cache.clone(),
390398
GlobalToLocalCopy::Allow,
399+
jsr_url(),
391400
);
392401
assert_eq!(
393402
String::from_utf8(
@@ -423,6 +432,7 @@ fn test_local_global_cache() {
423432
local_cache_path.clone(),
424433
global_cache.clone(),
425434
GlobalToLocalCopy::Allow,
435+
jsr_url(),
426436
);
427437

428438
// now try a redirect
@@ -461,6 +471,7 @@ fn test_local_global_cache() {
461471
local_cache_path.clone(),
462472
global_cache.clone(),
463473
GlobalToLocalCopy::Allow,
474+
jsr_url(),
464475
);
465476
let url = Url::parse("https://deno.land/x/mod.ts").unwrap();
466477
let matching_checksum =
@@ -530,6 +541,7 @@ fn test_lsp_local_cache() {
530541
local_cache_path.to_path_buf(),
531542
global_cache.clone(),
532543
GlobalToLocalCopy::Allow,
544+
jsr_url(),
533545
);
534546
let create_readonly_cache = || {
535547
LocalLspHttpCache::new(local_cache_path.to_path_buf(), global_cache.clone())

0 commit comments

Comments
 (0)