diff --git a/Cargo.lock b/Cargo.lock index fc87141562..4bf79e1fa5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1308,9 +1308,9 @@ dependencies = [ [[package]] name = "deno_config" -version = "0.22.2" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83df0c14d89f4e6e7ff91bfea0b4d5a0a33b4385c517ff4d8b4236d9834561e3" +checksum = "90684d387a893a3318569b8bb548e2f9291f86f2909f5349dd9d2b97c83fdb18" dependencies = [ "anyhow", "deno_semver", diff --git a/Cargo.toml b/Cargo.toml index 93e3043d26..d742d01dc4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,7 +101,7 @@ console_static_text = "=0.8.1" data-encoding = "2.3.3" data-url = "=0.3.0" deno_cache_dir = "=0.10.0" -deno_config = { version = "=0.22.2", default-features = false } +deno_config = { version = "=0.23.0", default-features = false } dlopen2 = "0.6.1" ecb = "=0.1.2" elliptic-curve = { version = "0.13.4", features = ["alloc", "arithmetic", "ecdh", "std", "pem"] } diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index d207b81a99..861a63d0c7 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -1117,6 +1117,7 @@ pub struct ConfigData { pub import_map_from_settings: bool, pub package_config: Option>, pub is_workspace_root: bool, + pub workspace_root_dir: ModuleSpecifier, /// Workspace member directories. For a workspace root this will be a list of /// members. For a member this will be the same list, representing self and /// siblings. For a solitary package this will be `vec![self.scope]`. These @@ -1532,28 +1533,37 @@ impl ConfigData { }) }); - let workspace = config_file + let workspace_config = config_file .as_ref() .and_then(|c| c.to_workspace_config().ok().flatten().map(|w| (c, w))); - let is_workspace_root = workspace.is_some(); - let workspace_members = if let Some((config, workspace)) = workspace { - Arc::new( - workspace - .members - .iter() - .flat_map(|p| { - let dir_specifier = config.specifier.join(p).ok()?; - let dir_path = specifier_to_file_path(&dir_specifier).ok()?; - Url::from_directory_path(normalize_path(dir_path)).ok() - }) - .collect(), - ) - } else if let Some(workspace_data) = workspace_root { - workspace_data.workspace_members.clone() - } else if config_file.as_ref().is_some_and(|c| c.json.name.is_some()) { - Arc::new(vec![scope.clone()]) + let is_workspace_root = workspace_config.is_some(); + let workspace_members = + if let Some((config, workspace_config)) = workspace_config { + Arc::new( + workspace_config + .members + .iter() + .flat_map(|p| { + let dir_specifier = config.specifier.join(p).ok()?; + let dir_path = specifier_to_file_path(&dir_specifier).ok()?; + Url::from_directory_path(normalize_path(dir_path)).ok() + }) + .collect(), + ) + } else if let Some(workspace_data) = workspace_root { + workspace_data.workspace_members.clone() + } else if config_file.as_ref().is_some_and(|c| c.json.name.is_some()) { + Arc::new(vec![scope.clone()]) + } else { + Arc::new(vec![]) + }; + let workspace_root_dir = if is_workspace_root { + scope.clone() } else { - Arc::new(vec![]) + workspace_root + .as_ref() + .map(|r| r.scope.clone()) + .unwrap_or_else(|| scope.clone()) }; ConfigData { @@ -1574,6 +1584,7 @@ impl ConfigData { import_map_from_settings, package_config: package_config.map(Arc::new), is_workspace_root, + workspace_root_dir, workspace_members, watched_files, } diff --git a/cli/lsp/resolver.rs b/cli/lsp/resolver.rs index 21b46255ca..2ca93114da 100644 --- a/cli/lsp/resolver.rs +++ b/cli/lsp/resolver.rs @@ -529,6 +529,13 @@ fn create_graph_resolver( node_resolver: node_resolver.cloned(), npm_resolver: npm_resolver.cloned(), workspace_resolver: Arc::new(WorkspaceResolver::new_raw( + Arc::new( + config_data + .map(|d| d.workspace_root_dir.clone()) + // this is fine because this value is only used to filter bare + // specifier resolution to workspace npm packages when in a workspace + .unwrap_or_else(|| ModuleSpecifier::parse("file:///").unwrap()), + ), config_data.and_then(|d| d.import_map.as_ref().map(|i| (**i).clone())), config_data .and_then(|d| d.package_json.clone()) diff --git a/cli/resolver.rs b/cli/resolver.rs index 6049ec2732..7c47795c45 100644 --- a/cli/resolver.rs +++ b/cli/resolver.rs @@ -536,6 +536,22 @@ impl Resolver for CliGraphResolver { Ok(resolution) => match resolution { MappedResolution::Normal(specifier) | MappedResolution::ImportMap(specifier) => Ok(specifier), + MappedResolution::WorkspaceNpmPackage { + target_pkg_json: pkg_json, + sub_path, + .. + } => self + .node_resolver + .as_ref() + .unwrap() + .resolve_package_sub_path_from_deno_module( + pkg_json.dir_path(), + sub_path.as_deref(), + Some(referrer), + to_node_mode(mode), + ) + .map_err(ResolveError::Other) + .map(|res| res.into_url()), // todo(dsherret): for byonm it should do resolution solely based on // the referrer and not the package.json MappedResolution::PackageJson { diff --git a/cli/standalone/mod.rs b/cli/standalone/mod.rs index 1df3895efd..e0c8e66ff0 100644 --- a/cli/standalone/mod.rs +++ b/cli/standalone/mod.rs @@ -88,7 +88,7 @@ struct WorkspaceEszipModule { struct WorkspaceEszip { eszip: eszip::EszipV2, - root_dir_url: ModuleSpecifier, + root_dir_url: Arc, } impl WorkspaceEszip { @@ -166,6 +166,22 @@ impl ModuleLoader for EmbeddedModuleLoader { self.shared.workspace_resolver.resolve(specifier, &referrer); match mapped_resolution { + Ok(MappedResolution::WorkspaceNpmPackage { + target_pkg_json: pkg_json, + sub_path, + .. + }) => Ok( + self + .shared + .node_resolver + .resolve_package_sub_path_from_deno_module( + pkg_json.dir_path(), + sub_path.as_deref(), + Some(&referrer), + NodeResolutionMode::Execution, + )? + .into_url(), + ), Ok(MappedResolution::PackageJson { dep_result, sub_path, @@ -427,7 +443,8 @@ pub async fn run( let npm_registry_url = ModuleSpecifier::parse("https://localhost/").unwrap(); let root_path = std::env::temp_dir().join(format!("deno-compile-{}", current_exe_name)); - let root_dir_url = ModuleSpecifier::from_directory_path(&root_path).unwrap(); + let root_dir_url = + Arc::new(ModuleSpecifier::from_directory_path(&root_path).unwrap()); let main_module = root_dir_url.join(&metadata.entrypoint_key).unwrap(); let root_node_modules_path = root_path.join("node_modules"); let npm_cache_dir = NpmCacheDir::new( @@ -579,6 +596,7 @@ pub async fn run( }) .collect(); WorkspaceResolver::new_raw( + root_dir_url.clone(), import_map, pkg_jsons, metadata.workspace_resolver.pkg_json_resolution, diff --git a/cli/tools/registry/unfurl.rs b/cli/tools/registry/unfurl.rs index 758db07964..63c773f018 100644 --- a/cli/tools/registry/unfurl.rs +++ b/cli/tools/registry/unfurl.rs @@ -75,26 +75,59 @@ impl SpecifierUnfurler { match resolved { MappedResolution::Normal(specifier) | MappedResolution::ImportMap(specifier) => Some(specifier), + MappedResolution::WorkspaceNpmPackage { + target_pkg_json: pkg_json, + pkg_name, + sub_path, + } => { + // todo(#24612): consider warning or error when this is also a jsr package? + ModuleSpecifier::parse(&format!( + "npm:{}{}{}", + pkg_name, + pkg_json + .version + .as_ref() + .map(|v| format!("@^{}", v)) + .unwrap_or_default(), + sub_path + .as_ref() + .map(|s| format!("/{}", s)) + .unwrap_or_default() + )) + .ok() + } MappedResolution::PackageJson { + alias, sub_path, dep_result, .. } => match dep_result { Ok(dep) => match dep { - PackageJsonDepValue::Req(req) => ModuleSpecifier::parse(&format!( - "npm:{}{}", - req, - sub_path - .as_ref() - .map(|s| format!("/{}", s)) - .unwrap_or_default() - )) - .ok(), - PackageJsonDepValue::Workspace(_) => { - log::warn!( - "package.json workspace entries are not implemented yet for publishing." - ); - None + PackageJsonDepValue::Req(pkg_req) => { + // todo(#24612): consider warning or error when this is an npm workspace + // member that's also a jsr package? + ModuleSpecifier::parse(&format!( + "npm:{}{}", + pkg_req, + sub_path + .as_ref() + .map(|s| format!("/{}", s)) + .unwrap_or_default() + )) + .ok() + } + PackageJsonDepValue::Workspace(version_req) => { + // todo(#24612): consider warning or error when this is also a jsr package? + ModuleSpecifier::parse(&format!( + "npm:{}@{}{}", + alias, + version_req, + sub_path + .as_ref() + .map(|s| format!("/{}", s)) + .unwrap_or_default() + )) + .ok() } }, Err(err) => { @@ -401,6 +434,7 @@ mod tests { }), ); let workspace_resolver = WorkspaceResolver::new_raw( + Arc::new(ModuleSpecifier::from_directory_path(&cwd).unwrap()), Some(import_map), vec![Arc::new(package_json)], deno_config::workspace::PackageJsonDepResolution::Enabled, diff --git a/cli/tools/vendor/test.rs b/cli/tools/vendor/test.rs index ac07c47d17..dc851858e1 100644 --- a/cli/tools/vendor/test.rs +++ b/cli/tools/vendor/test.rs @@ -234,6 +234,7 @@ impl VendorTestBuilder { let loader = self.loader.clone(); let parsed_source_cache = ParsedSourceCache::default(); let resolver = Arc::new(build_resolver( + output_dir.parent().unwrap(), self.jsx_import_source_config.clone(), self.maybe_original_import_map.clone(), )); @@ -287,6 +288,7 @@ impl VendorTestBuilder { } fn build_resolver( + root_dir: &Path, maybe_jsx_import_source_config: Option, maybe_original_import_map: Option, ) -> CliGraphResolver { @@ -295,6 +297,7 @@ fn build_resolver( npm_resolver: None, sloppy_imports_resolver: None, workspace_resolver: Arc::new(WorkspaceResolver::new_raw( + Arc::new(ModuleSpecifier::from_directory_path(root_dir).unwrap()), maybe_original_import_map, Vec::new(), deno_config::workspace::PackageJsonDepResolution::Enabled, diff --git a/tests/specs/npm/workspace_basic/__test__.jsonc b/tests/specs/npm/workspace_basic/__test__.jsonc index 79e059ca18..7415a9aeb2 100644 --- a/tests/specs/npm/workspace_basic/__test__.jsonc +++ b/tests/specs/npm/workspace_basic/__test__.jsonc @@ -5,6 +5,10 @@ "args": "run --node-modules-dir=false b/main.ts", "output": "b/main_global_cache.out" }, + "global_cache_bare_specifier_not_in_pkg": { + "args": "run --node-modules-dir=false main.ts", + "output": "main.out" + }, "node_modules_dir": { "args": "run --node-modules-dir=true b/main.ts", "output": "b/main_node_modules_dir.out" diff --git a/tests/specs/npm/workspace_basic/b/main.ts b/tests/specs/npm/workspace_basic/b/main.ts index 03956388c3..4920b8d6f3 100644 --- a/tests/specs/npm/workspace_basic/b/main.ts +++ b/tests/specs/npm/workspace_basic/b/main.ts @@ -1,9 +1,7 @@ import * as a1 from "@denotest/a"; import * as a2 from "npm:@denotest/a@1"; -import * as a3 from "npm:@denotest/a@workspace"; import * as c from "@denotest/c"; a1.sayHello(); a2.sayHello(); -a3.sayHello(); c.sayHello(); diff --git a/tests/specs/npm/workspace_basic/b/main_byonm.out b/tests/specs/npm/workspace_basic/b/main_byonm.out index 3a311dcd7e..6520666f7a 100644 --- a/tests/specs/npm/workspace_basic/b/main_byonm.out +++ b/tests/specs/npm/workspace_basic/b/main_byonm.out @@ -1,4 +1,3 @@ Hello 5 Hello 5 -Hello 5 C: Hi! diff --git a/tests/specs/npm/workspace_basic/b/main_global_cache.out b/tests/specs/npm/workspace_basic/b/main_global_cache.out index 1ca11026a2..203ac5a991 100644 --- a/tests/specs/npm/workspace_basic/b/main_global_cache.out +++ b/tests/specs/npm/workspace_basic/b/main_global_cache.out @@ -2,5 +2,4 @@ Download http://localhost:4260/@denotest/esm-basic Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Hello 5 Hello 5 -Hello 5 C: Hi! diff --git a/tests/specs/npm/workspace_basic/b/main_node_modules_dir.out b/tests/specs/npm/workspace_basic/b/main_node_modules_dir.out index 82a49b9fe6..eeb4556526 100644 --- a/tests/specs/npm/workspace_basic/b/main_node_modules_dir.out +++ b/tests/specs/npm/workspace_basic/b/main_node_modules_dir.out @@ -3,5 +3,4 @@ Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz Initialize @denotest/esm-basic@1.0.0 Hello 5 Hello 5 -Hello 5 C: Hi! diff --git a/tests/specs/npm/workspace_basic/main.out b/tests/specs/npm/workspace_basic/main.out new file mode 100644 index 0000000000..92404af878 --- /dev/null +++ b/tests/specs/npm/workspace_basic/main.out @@ -0,0 +1,4 @@ +Download http://localhost:4260/@denotest/esm-basic +Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz +Hello 5 +C: Hi! diff --git a/tests/specs/npm/workspace_basic/main.ts b/tests/specs/npm/workspace_basic/main.ts new file mode 100644 index 0000000000..316503b9ca --- /dev/null +++ b/tests/specs/npm/workspace_basic/main.ts @@ -0,0 +1,6 @@ +// should resolve these as bare specifiers within the workspace +import * as a from "@denotest/a"; +import * as c from "@denotest/c"; + +a.sayHello(); +c.sayHello(); diff --git a/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/__test__.jsonc b/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/__test__.jsonc new file mode 100644 index 0000000000..8819e630dd --- /dev/null +++ b/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/__test__.jsonc @@ -0,0 +1,14 @@ +{ + "tests": { + "dep_and_workspace_dep": { + "args": "publish --dry-run --no-check --log-level=debug", + "cwd": "subtract", + "output": "publish-subtract.out" + }, + "bare_specifier": { + "args": "publish --dry-run --no-check --log-level=debug", + "cwd": "subtract-2", + "output": "publish-subtract2.out" + } + } +} diff --git a/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/add/index.js b/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/add/index.js new file mode 100644 index 0000000000..7d658310b0 --- /dev/null +++ b/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/add/index.js @@ -0,0 +1,3 @@ +export function add(a, b) { + return a + b; +} diff --git a/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/add/package.json b/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/add/package.json new file mode 100644 index 0000000000..ecbc84a26f --- /dev/null +++ b/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/add/package.json @@ -0,0 +1,6 @@ +{ + "name": "add", + "type": "module", + "version": "1.0.0", + "exports": "./index.js" +} diff --git a/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/package.json b/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/package.json new file mode 100644 index 0000000000..0f6d53f870 --- /dev/null +++ b/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/package.json @@ -0,0 +1,3 @@ +{ + "workspaces": ["./add", "./subtract", "./subtract-2"] +} diff --git a/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/publish-subtract.out b/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/publish-subtract.out new file mode 100644 index 0000000000..89f78c44c5 --- /dev/null +++ b/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/publish-subtract.out @@ -0,0 +1,3 @@ +[WILDCARD]Unfurled specifier: add-dep from file:///[WILDLINE]/subtract/index.ts -> npm:add@1.0.0 +[WILDCARD]Unfurled specifier: add from file:///[WILDLINE]/subtract/index.ts -> npm:add@^1.0.0 +[WILDCARD] \ No newline at end of file diff --git a/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/publish-subtract2.out b/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/publish-subtract2.out new file mode 100644 index 0000000000..9edadaf675 --- /dev/null +++ b/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/publish-subtract2.out @@ -0,0 +1,2 @@ +[WILDCARD]Unfurled specifier: add from file:///[WILDLINE]/subtract-2/index.ts -> npm:add@^1.0.0 +[WILDCARD] \ No newline at end of file diff --git a/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/subtract-2/deno.json b/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/subtract-2/deno.json new file mode 100644 index 0000000000..edae3158b7 --- /dev/null +++ b/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/subtract-2/deno.json @@ -0,0 +1,5 @@ +{ + "name": "@scope/subtract2", + "version": "1.0.0", + "exports": "./index.ts" +} diff --git a/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/subtract-2/index.ts b/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/subtract-2/index.ts new file mode 100644 index 0000000000..aa80c50b5c --- /dev/null +++ b/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/subtract-2/index.ts @@ -0,0 +1,6 @@ +// test using a bare specifier to a pkg.json dep +import { add } from "add"; + +export function subtract(a: number, b: number): number { + return add(a, -b); +} diff --git a/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/subtract-2/package.json b/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/subtract-2/package.json new file mode 100644 index 0000000000..1cf48f2643 --- /dev/null +++ b/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/subtract-2/package.json @@ -0,0 +1,4 @@ +{ + "name": "subtract2", + "version": "1.0.0" +} diff --git a/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/subtract/deno.json b/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/subtract/deno.json new file mode 100644 index 0000000000..cb003e3740 --- /dev/null +++ b/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/subtract/deno.json @@ -0,0 +1,5 @@ +{ + "name": "@scope/subtract", + "version": "1.0.0", + "exports": "./index.ts" +} diff --git a/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/subtract/index.ts b/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/subtract/index.ts new file mode 100644 index 0000000000..11f0cf4e83 --- /dev/null +++ b/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/subtract/index.ts @@ -0,0 +1,7 @@ +// test using a pkg.json dep and a workspace dep +import * as addDep from "add-dep"; +import * as addWorkspaceDep from "add"; + +export function subtract(a: number, b: number): number { + return addWorkspaceDep.add(addDep.add(a, -b), 1 - 1); +} diff --git a/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/subtract/package.json b/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/subtract/package.json new file mode 100644 index 0000000000..7d6b0f8775 --- /dev/null +++ b/tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/subtract/package.json @@ -0,0 +1,8 @@ +{ + "name": "subtract", + "version": "1.0.0", + "dependencies": { + "add-dep": "npm:add@1.0.0", + "add": "workspace:^1.0.0" + } +}