Skip to content

Commit a84ca67

Browse files
dsherretbartlomieju
authored andcommitted
fix(workspace): support resolving bare specifiers to npm pkgs within a workspace (#24611)
This makes bare specifiers for npm packages work when inside a workspace, which emulates the same behaviour as when there's a node_modules directory. The bare specifier can be overwritten by specifying an import map entry or package.json dependency entry. * denoland/deno_config#88 Closes #24605
1 parent 3628895 commit a84ca67

File tree

27 files changed

+207
-43
lines changed

27 files changed

+207
-43
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ console_static_text = "=0.8.1"
101101
data-encoding = "2.3.3"
102102
data-url = "=0.3.0"
103103
deno_cache_dir = "=0.10.0"
104-
deno_config = { version = "=0.22.2", default-features = false }
104+
deno_config = { version = "=0.23.0", default-features = false }
105105
dlopen2 = "0.6.1"
106106
ecb = "=0.1.2"
107107
elliptic-curve = { version = "0.13.4", features = ["alloc", "arithmetic", "ecdh", "std", "pem"] }

cli/lsp/config.rs

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,6 +1117,7 @@ pub struct ConfigData {
11171117
pub import_map_from_settings: bool,
11181118
pub package_config: Option<Arc<LspPackageConfig>>,
11191119
pub is_workspace_root: bool,
1120+
pub workspace_root_dir: ModuleSpecifier,
11201121
/// Workspace member directories. For a workspace root this will be a list of
11211122
/// members. For a member this will be the same list, representing self and
11221123
/// siblings. For a solitary package this will be `vec![self.scope]`. These
@@ -1532,28 +1533,37 @@ impl ConfigData {
15321533
})
15331534
});
15341535

1535-
let workspace = config_file
1536+
let workspace_config = config_file
15361537
.as_ref()
15371538
.and_then(|c| c.to_workspace_config().ok().flatten().map(|w| (c, w)));
1538-
let is_workspace_root = workspace.is_some();
1539-
let workspace_members = if let Some((config, workspace)) = workspace {
1540-
Arc::new(
1541-
workspace
1542-
.members
1543-
.iter()
1544-
.flat_map(|p| {
1545-
let dir_specifier = config.specifier.join(p).ok()?;
1546-
let dir_path = specifier_to_file_path(&dir_specifier).ok()?;
1547-
Url::from_directory_path(normalize_path(dir_path)).ok()
1548-
})
1549-
.collect(),
1550-
)
1551-
} else if let Some(workspace_data) = workspace_root {
1552-
workspace_data.workspace_members.clone()
1553-
} else if config_file.as_ref().is_some_and(|c| c.json.name.is_some()) {
1554-
Arc::new(vec![scope.clone()])
1539+
let is_workspace_root = workspace_config.is_some();
1540+
let workspace_members =
1541+
if let Some((config, workspace_config)) = workspace_config {
1542+
Arc::new(
1543+
workspace_config
1544+
.members
1545+
.iter()
1546+
.flat_map(|p| {
1547+
let dir_specifier = config.specifier.join(p).ok()?;
1548+
let dir_path = specifier_to_file_path(&dir_specifier).ok()?;
1549+
Url::from_directory_path(normalize_path(dir_path)).ok()
1550+
})
1551+
.collect(),
1552+
)
1553+
} else if let Some(workspace_data) = workspace_root {
1554+
workspace_data.workspace_members.clone()
1555+
} else if config_file.as_ref().is_some_and(|c| c.json.name.is_some()) {
1556+
Arc::new(vec![scope.clone()])
1557+
} else {
1558+
Arc::new(vec![])
1559+
};
1560+
let workspace_root_dir = if is_workspace_root {
1561+
scope.clone()
15551562
} else {
1556-
Arc::new(vec![])
1563+
workspace_root
1564+
.as_ref()
1565+
.map(|r| r.scope.clone())
1566+
.unwrap_or_else(|| scope.clone())
15571567
};
15581568

15591569
ConfigData {
@@ -1574,6 +1584,7 @@ impl ConfigData {
15741584
import_map_from_settings,
15751585
package_config: package_config.map(Arc::new),
15761586
is_workspace_root,
1587+
workspace_root_dir,
15771588
workspace_members,
15781589
watched_files,
15791590
}

cli/lsp/resolver.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,13 @@ fn create_graph_resolver(
529529
node_resolver: node_resolver.cloned(),
530530
npm_resolver: npm_resolver.cloned(),
531531
workspace_resolver: Arc::new(WorkspaceResolver::new_raw(
532+
Arc::new(
533+
config_data
534+
.map(|d| d.workspace_root_dir.clone())
535+
// this is fine because this value is only used to filter bare
536+
// specifier resolution to workspace npm packages when in a workspace
537+
.unwrap_or_else(|| ModuleSpecifier::parse("file:///").unwrap()),
538+
),
532539
config_data.and_then(|d| d.import_map.as_ref().map(|i| (**i).clone())),
533540
config_data
534541
.and_then(|d| d.package_json.clone())

cli/resolver.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,22 @@ impl Resolver for CliGraphResolver {
536536
Ok(resolution) => match resolution {
537537
MappedResolution::Normal(specifier)
538538
| MappedResolution::ImportMap(specifier) => Ok(specifier),
539+
MappedResolution::WorkspaceNpmPackage {
540+
target_pkg_json: pkg_json,
541+
sub_path,
542+
..
543+
} => self
544+
.node_resolver
545+
.as_ref()
546+
.unwrap()
547+
.resolve_package_sub_path_from_deno_module(
548+
pkg_json.dir_path(),
549+
sub_path.as_deref(),
550+
Some(referrer),
551+
to_node_mode(mode),
552+
)
553+
.map_err(ResolveError::Other)
554+
.map(|res| res.into_url()),
539555
// todo(dsherret): for byonm it should do resolution solely based on
540556
// the referrer and not the package.json
541557
MappedResolution::PackageJson {

cli/standalone/mod.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ struct WorkspaceEszipModule {
8888

8989
struct WorkspaceEszip {
9090
eszip: eszip::EszipV2,
91-
root_dir_url: ModuleSpecifier,
91+
root_dir_url: Arc<ModuleSpecifier>,
9292
}
9393

9494
impl WorkspaceEszip {
@@ -166,6 +166,22 @@ impl ModuleLoader for EmbeddedModuleLoader {
166166
self.shared.workspace_resolver.resolve(specifier, &referrer);
167167

168168
match mapped_resolution {
169+
Ok(MappedResolution::WorkspaceNpmPackage {
170+
target_pkg_json: pkg_json,
171+
sub_path,
172+
..
173+
}) => Ok(
174+
self
175+
.shared
176+
.node_resolver
177+
.resolve_package_sub_path_from_deno_module(
178+
pkg_json.dir_path(),
179+
sub_path.as_deref(),
180+
Some(&referrer),
181+
NodeResolutionMode::Execution,
182+
)?
183+
.into_url(),
184+
),
169185
Ok(MappedResolution::PackageJson {
170186
dep_result,
171187
sub_path,
@@ -427,7 +443,8 @@ pub async fn run(
427443
let npm_registry_url = ModuleSpecifier::parse("https://localhost/").unwrap();
428444
let root_path =
429445
std::env::temp_dir().join(format!("deno-compile-{}", current_exe_name));
430-
let root_dir_url = ModuleSpecifier::from_directory_path(&root_path).unwrap();
446+
let root_dir_url =
447+
Arc::new(ModuleSpecifier::from_directory_path(&root_path).unwrap());
431448
let main_module = root_dir_url.join(&metadata.entrypoint_key).unwrap();
432449
let root_node_modules_path = root_path.join("node_modules");
433450
let npm_cache_dir = NpmCacheDir::new(
@@ -579,6 +596,7 @@ pub async fn run(
579596
})
580597
.collect();
581598
WorkspaceResolver::new_raw(
599+
root_dir_url.clone(),
582600
import_map,
583601
pkg_jsons,
584602
metadata.workspace_resolver.pkg_json_resolution,

cli/tools/registry/unfurl.rs

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -75,26 +75,59 @@ impl SpecifierUnfurler {
7575
match resolved {
7676
MappedResolution::Normal(specifier)
7777
| MappedResolution::ImportMap(specifier) => Some(specifier),
78+
MappedResolution::WorkspaceNpmPackage {
79+
target_pkg_json: pkg_json,
80+
pkg_name,
81+
sub_path,
82+
} => {
83+
// todo(#24612): consider warning or error when this is also a jsr package?
84+
ModuleSpecifier::parse(&format!(
85+
"npm:{}{}{}",
86+
pkg_name,
87+
pkg_json
88+
.version
89+
.as_ref()
90+
.map(|v| format!("@^{}", v))
91+
.unwrap_or_default(),
92+
sub_path
93+
.as_ref()
94+
.map(|s| format!("/{}", s))
95+
.unwrap_or_default()
96+
))
97+
.ok()
98+
}
7899
MappedResolution::PackageJson {
100+
alias,
79101
sub_path,
80102
dep_result,
81103
..
82104
} => match dep_result {
83105
Ok(dep) => match dep {
84-
PackageJsonDepValue::Req(req) => ModuleSpecifier::parse(&format!(
85-
"npm:{}{}",
86-
req,
87-
sub_path
88-
.as_ref()
89-
.map(|s| format!("/{}", s))
90-
.unwrap_or_default()
91-
))
92-
.ok(),
93-
PackageJsonDepValue::Workspace(_) => {
94-
log::warn!(
95-
"package.json workspace entries are not implemented yet for publishing."
96-
);
97-
None
106+
PackageJsonDepValue::Req(pkg_req) => {
107+
// todo(#24612): consider warning or error when this is an npm workspace
108+
// member that's also a jsr package?
109+
ModuleSpecifier::parse(&format!(
110+
"npm:{}{}",
111+
pkg_req,
112+
sub_path
113+
.as_ref()
114+
.map(|s| format!("/{}", s))
115+
.unwrap_or_default()
116+
))
117+
.ok()
118+
}
119+
PackageJsonDepValue::Workspace(version_req) => {
120+
// todo(#24612): consider warning or error when this is also a jsr package?
121+
ModuleSpecifier::parse(&format!(
122+
"npm:{}@{}{}",
123+
alias,
124+
version_req,
125+
sub_path
126+
.as_ref()
127+
.map(|s| format!("/{}", s))
128+
.unwrap_or_default()
129+
))
130+
.ok()
98131
}
99132
},
100133
Err(err) => {
@@ -401,6 +434,7 @@ mod tests {
401434
}),
402435
);
403436
let workspace_resolver = WorkspaceResolver::new_raw(
437+
Arc::new(ModuleSpecifier::from_directory_path(&cwd).unwrap()),
404438
Some(import_map),
405439
vec![Arc::new(package_json)],
406440
deno_config::workspace::PackageJsonDepResolution::Enabled,

cli/tools/vendor/test.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ impl VendorTestBuilder {
234234
let loader = self.loader.clone();
235235
let parsed_source_cache = ParsedSourceCache::default();
236236
let resolver = Arc::new(build_resolver(
237+
output_dir.parent().unwrap(),
237238
self.jsx_import_source_config.clone(),
238239
self.maybe_original_import_map.clone(),
239240
));
@@ -287,6 +288,7 @@ impl VendorTestBuilder {
287288
}
288289

289290
fn build_resolver(
291+
root_dir: &Path,
290292
maybe_jsx_import_source_config: Option<JsxImportSourceConfig>,
291293
maybe_original_import_map: Option<ImportMap>,
292294
) -> CliGraphResolver {
@@ -295,6 +297,7 @@ fn build_resolver(
295297
npm_resolver: None,
296298
sloppy_imports_resolver: None,
297299
workspace_resolver: Arc::new(WorkspaceResolver::new_raw(
300+
Arc::new(ModuleSpecifier::from_directory_path(root_dir).unwrap()),
298301
maybe_original_import_map,
299302
Vec::new(),
300303
deno_config::workspace::PackageJsonDepResolution::Enabled,

tests/specs/npm/workspace_basic/__test__.jsonc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
"args": "run --node-modules-dir=false b/main.ts",
66
"output": "b/main_global_cache.out"
77
},
8+
"global_cache_bare_specifier_not_in_pkg": {
9+
"args": "run --node-modules-dir=false main.ts",
10+
"output": "main.out"
11+
},
812
"node_modules_dir": {
913
"args": "run --node-modules-dir=true b/main.ts",
1014
"output": "b/main_node_modules_dir.out"
Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import * as a1 from "@denotest/a";
22
import * as a2 from "npm:@denotest/a@1";
3-
import * as a3 from "npm:@denotest/a@workspace";
43
import * as c from "@denotest/c";
54

65
a1.sayHello();
76
a2.sayHello();
8-
a3.sayHello();
97
c.sayHello();

0 commit comments

Comments
 (0)