Skip to content

Commit 6087c31

Browse files
committed
fix: local files at uneven dir depths
1 parent 35d72b5 commit 6087c31

3 files changed

Lines changed: 243 additions & 24 deletions

File tree

rs-lib/src/lib.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,12 @@ pub async fn transform(
461461
})
462462
.await?;
463463

464-
let mappings = Mappings::new(&module_graph, &specifiers)?;
464+
let mappings = Mappings::new(
465+
&module_graph,
466+
&specifiers,
467+
&options.entry_points,
468+
&options.test_entry_points,
469+
)?;
465470
let all_package_specifier_mappings: HashMap<ModuleSpecifier, String> =
466471
specifiers
467472
.main

rs-lib/src/mappings.rs

Lines changed: 97 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -46,27 +46,67 @@ impl Mappings {
4646
pub fn new(
4747
module_graph: &ModuleGraph,
4848
specifiers: &Specifiers,
49+
main_entry_points: &[ModuleSpecifier],
50+
test_entry_points: &[ModuleSpecifier],
4951
) -> Result<Self> {
5052
let mut mappings = HashMap::new();
5153
let mut mapped_filepaths_no_ext = HashSet::new();
52-
let base_dir = get_base_dir(&specifiers.local)?;
54+
let local_main_entry_points = local_entry_points(main_entry_points);
55+
let local_test_entry_points = local_entry_points(test_entry_points);
56+
let main_base_dir = if local_main_entry_points.is_empty() {
57+
get_base_dir(&specifiers.local)?
58+
} else {
59+
get_base_dir(&local_main_entry_points)?
60+
};
61+
let mut test_base_dir_candidates = vec![main_base_dir.clone()];
62+
for specifier in &local_test_entry_points {
63+
let file_path = url_to_file_path(specifier)?;
64+
test_base_dir_candidates.push(file_path.parent().unwrap().to_path_buf());
65+
}
66+
let test_base_dir = get_common_dir(test_base_dir_candidates);
67+
ensure_nonempty_base_dir(&test_base_dir)?;
68+
let mut external_base_dir_candidates =
69+
vec![main_base_dir.clone(), test_base_dir.clone()];
70+
for specifier in &specifiers.local {
71+
let file_path = url_to_file_path(specifier)?;
72+
let base_dir = if specifiers.test_modules.contains(specifier) {
73+
&test_base_dir
74+
} else {
75+
&main_base_dir
76+
};
77+
if !file_path.starts_with(base_dir) {
78+
external_base_dir_candidates
79+
.push(file_path.parent().unwrap().to_path_buf());
80+
}
81+
}
82+
let external_base_dir = get_common_dir(external_base_dir_candidates);
83+
ensure_nonempty_base_dir(&external_base_dir)?;
5384
let mut root_local_dirs = HashSet::new();
5485

5586
for specifier in specifiers.local.iter() {
5687
let file_path = url_to_file_path(specifier)?;
57-
let relative_file_path =
58-
file_path.strip_prefix(&base_dir).map_err(|_| {
59-
anyhow::anyhow!(
60-
"Error stripping prefix of {} with base {}",
61-
file_path.display(),
62-
base_dir.display()
63-
)
64-
})?;
88+
let base_dir = if specifiers.test_modules.contains(specifier) {
89+
&test_base_dir
90+
} else {
91+
&main_base_dir
92+
};
93+
let relative_file_path = match file_path.strip_prefix(base_dir) {
94+
Ok(path) => path.to_path_buf(),
95+
Err(_) => PathBuf::from("deps").join(
96+
file_path.strip_prefix(&external_base_dir).map_err(|_| {
97+
anyhow::anyhow!(
98+
"Error stripping prefix of {} with base {}",
99+
file_path.display(),
100+
external_base_dir.display()
101+
)
102+
})?,
103+
),
104+
};
65105
mappings.insert(
66106
specifier.clone(),
67107
get_mapped_file_path(
68-
MediaType::from_path(relative_file_path),
69-
relative_file_path,
108+
MediaType::from_path(&relative_file_path),
109+
&relative_file_path,
70110
&mut mapped_filepaths_no_ext,
71111
),
72112
);
@@ -187,6 +227,16 @@ impl Mappings {
187227
}
188228
}
189229

230+
fn local_entry_points(
231+
entry_points: &[ModuleSpecifier],
232+
) -> Vec<ModuleSpecifier> {
233+
entry_points
234+
.iter()
235+
.filter(|specifier| specifier.scheme() == "file")
236+
.cloned()
237+
.collect()
238+
}
239+
190240
/// Takes a group of remote specifiers for the provided base directory
191241
/// and gets their output paths.
192242
fn remote_specifiers_to_paths<'a>(
@@ -499,20 +549,37 @@ fn get_base_dir(specifiers: &[ModuleSpecifier]) -> Result<PathBuf> {
499549
if specifiers.is_empty() {
500550
bail!("Did not find any local files. Specifying only remote files is not currently supported.");
501551
}
552+
let base_dir = get_common_dir(
553+
specifiers
554+
.iter()
555+
.map(|specifier| {
556+
Ok(url_to_file_path(specifier)?.parent().unwrap().into())
557+
})
558+
.collect::<Result<Vec<PathBuf>>>()?,
559+
);
560+
ensure_nonempty_base_dir(&base_dir)?;
561+
Ok(base_dir)
562+
}
563+
564+
fn ensure_nonempty_base_dir(base_dir: &Path) -> Result<()> {
565+
if base_dir.as_os_str().is_empty() {
566+
bail!("Local files span different filesystem roots.");
567+
}
568+
Ok(())
569+
}
570+
571+
fn get_common_dir(dirs: impl IntoIterator<Item = PathBuf>) -> PathBuf {
502572
// todo(dsherret): should maybe error on windows when the files
503573
// span different drives...
504-
let mut base_dir = url_to_file_path(&specifiers[0])?
505-
.parent()
506-
.unwrap()
507-
.to_path_buf();
508-
for specifier in specifiers {
509-
let file_path = url_to_file_path(specifier)?;
510-
let parent_dir = file_path.parent().unwrap();
574+
let mut dirs = dirs.into_iter();
575+
let Some(mut base_dir) = dirs.next() else {
576+
return PathBuf::new();
577+
};
578+
for parent_dir in dirs {
511579
if base_dir != parent_dir {
512-
if base_dir.starts_with(parent_dir) {
513-
base_dir = parent_dir.to_path_buf();
514-
} else if base_dir.components().count() == parent_dir.components().count()
515-
{
580+
if base_dir.starts_with(&parent_dir) {
581+
base_dir = parent_dir;
582+
} else {
516583
let mut final_path = PathBuf::new();
517584
for (a, b) in base_dir.components().zip(parent_dir.components()) {
518585
if a == b {
@@ -525,7 +592,7 @@ fn get_base_dir(specifiers: &[ModuleSpecifier]) -> Result<PathBuf> {
525592
}
526593
}
527594
}
528-
Ok(base_dir)
595+
base_dir
529596
}
530597

531598
#[cfg(test)]
@@ -553,6 +620,13 @@ mod test {
553620
vec!["file:///project/b/other.ts", "file:///project/a/other.ts"],
554621
"/project",
555622
);
623+
run_test(
624+
vec![
625+
"file:///project-one/mod.ts",
626+
"file:///project-two/deeper/mod.ts",
627+
],
628+
"/",
629+
);
556630

557631
fn run_test(urls: Vec<&str>, expected: &str) {
558632
let result = get_base_dir(

rs-lib/tests/integration_test.rs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1449,6 +1449,146 @@ async fn transform_import_map() {
14491449
);
14501450
}
14511451

1452+
#[tokio::test]
1453+
async fn transform_import_map_places_uneven_siblings_in_deps() {
1454+
let result = TestBuilder::new()
1455+
.entry_point("file:///project-one/mod.ts")
1456+
.with_loader(|loader| {
1457+
loader
1458+
.add_local_file(
1459+
"/project-one/mod.ts",
1460+
"import value from 'project-two/deeper/mod.ts';",
1461+
)
1462+
.add_local_file(
1463+
"/project-one/deno.json",
1464+
r#"{
1465+
"imports": {
1466+
"project-two/": "../project-two/"
1467+
}
1468+
}"#,
1469+
)
1470+
.add_local_file(
1471+
"/project-two/deeper/mod.ts",
1472+
"export default 'hello world';",
1473+
);
1474+
})
1475+
.set_import_map("file:///project-one/deno.json")
1476+
.transform()
1477+
.await
1478+
.unwrap();
1479+
1480+
assert_files!(
1481+
result.main.files,
1482+
&[
1483+
(
1484+
"mod.ts",
1485+
"import value from './deps/project-two/deeper/mod.js';",
1486+
),
1487+
(
1488+
"deps/project-two/deeper/mod.ts",
1489+
"export default 'hello world';",
1490+
),
1491+
]
1492+
);
1493+
}
1494+
1495+
#[tokio::test]
1496+
async fn transform_import_map_keeps_entry_point_root_for_local_siblings() {
1497+
let result = TestBuilder::new()
1498+
.entry_point("file:///example-project/mod.ts")
1499+
.with_loader(|loader| {
1500+
loader
1501+
.add_local_file(
1502+
"/example-project/mod.ts",
1503+
"export * from './src/mod.ts';",
1504+
)
1505+
.add_local_file(
1506+
"/example-project/src/mod.ts",
1507+
"export * from 'example-shared/mod.ts';\nexport * from 'https://example.com/mod.ts';",
1508+
)
1509+
.add_local_file(
1510+
"/example-project/deno.json",
1511+
r#"{
1512+
"imports": {
1513+
"example-shared/": "../example-shared/"
1514+
}
1515+
}"#,
1516+
)
1517+
.add_local_file("/example-shared/mod.ts", "export const shared = 'shared';")
1518+
.add_remote_file("https://example.com/mod.ts", "export const remote = 'remote';");
1519+
})
1520+
.set_import_map("file:///example-project/deno.json")
1521+
.transform()
1522+
.await
1523+
.unwrap();
1524+
1525+
assert_files!(
1526+
result.main.files,
1527+
&[
1528+
("mod.ts", "export * from './src/mod.js';"),
1529+
(
1530+
"src/mod.ts",
1531+
"export * from '../deps/example-shared/mod.js';\nexport * from '../deps_2/example.com/mod.js';",
1532+
),
1533+
("deps/example-shared/mod.ts", "export const shared = 'shared';"),
1534+
(
1535+
"deps_2/example.com/mod.ts",
1536+
"export const remote = 'remote';",
1537+
),
1538+
]
1539+
);
1540+
assert_eq!(result.main.entry_points, &[PathBuf::from("mod.ts")]);
1541+
}
1542+
1543+
#[tokio::test]
1544+
async fn transform_test_entry_point_uses_test_root_without_moving_main() {
1545+
let result = TestBuilder::new()
1546+
.entry_point("file:///example-project/src/mod.ts")
1547+
.with_loader(|loader| {
1548+
loader
1549+
.add_local_file(
1550+
"/example-project/src/mod.ts",
1551+
"export * from 'example-shared/mod.ts';",
1552+
)
1553+
.add_local_file(
1554+
"/example-project/tests/integration/functions/mod.test.ts",
1555+
"import '../../../src/mod.ts';",
1556+
)
1557+
.add_local_file(
1558+
"/example-project/deno.json",
1559+
r#"{
1560+
"imports": {
1561+
"example-shared/": "../example-shared/"
1562+
}
1563+
}"#,
1564+
)
1565+
.add_local_file(
1566+
"/example-shared/mod.ts",
1567+
"export const shared = 'shared';",
1568+
);
1569+
})
1570+
.add_test_entry_point(
1571+
"file:///example-project/tests/integration/functions/mod.test.ts",
1572+
)
1573+
.set_import_map("file:///example-project/deno.json")
1574+
.transform()
1575+
.await
1576+
.unwrap();
1577+
1578+
assert_eq!(result.main.entry_points, &[PathBuf::from("mod.ts")]);
1579+
assert_eq!(
1580+
result.test.entry_points,
1581+
&[PathBuf::from("tests/integration/functions/mod.test.ts")]
1582+
);
1583+
assert_files!(
1584+
result.test.files,
1585+
&[(
1586+
"tests/integration/functions/mod.test.ts",
1587+
"import '../../../mod.js';",
1588+
)]
1589+
);
1590+
}
1591+
14521592
#[tokio::test]
14531593
async fn transform_config_file() {
14541594
let result = TestBuilder::new()

0 commit comments

Comments
 (0)