summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Sherret <dsherret@users.noreply.github.com>2024-07-17 09:13:22 -0400
committerGitHub <noreply@github.com>2024-07-17 09:13:22 -0400
commitf4b9d8586215fc07c28998e5d896fefa876139b7 (patch)
tree9ee42eb4bb62af04b1c3b049cd179dfa6fe908bb
parent078def0ff8501bb07f3f286515acd8c6a2181037 (diff)
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. * https://github.com/denoland/deno_config/pull/88 Closes #24605
-rw-r--r--Cargo.lock4
-rw-r--r--Cargo.toml2
-rw-r--r--cli/lsp/config.rs49
-rw-r--r--cli/lsp/resolver.rs7
-rw-r--r--cli/resolver.rs16
-rw-r--r--cli/standalone/mod.rs22
-rw-r--r--cli/tools/registry/unfurl.rs62
-rw-r--r--cli/tools/vendor/test.rs3
-rw-r--r--tests/specs/npm/workspace_basic/__test__.jsonc4
-rw-r--r--tests/specs/npm/workspace_basic/b/main.ts2
-rw-r--r--tests/specs/npm/workspace_basic/b/main_byonm.out1
-rw-r--r--tests/specs/npm/workspace_basic/b/main_global_cache.out1
-rw-r--r--tests/specs/npm/workspace_basic/b/main_node_modules_dir.out1
-rw-r--r--tests/specs/npm/workspace_basic/main.out4
-rw-r--r--tests/specs/npm/workspace_basic/main.ts6
-rw-r--r--tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/__test__.jsonc14
-rw-r--r--tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/add/index.js3
-rw-r--r--tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/add/package.json6
-rw-r--r--tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/package.json3
-rw-r--r--tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/publish-subtract.out3
-rw-r--r--tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/publish-subtract2.out2
-rw-r--r--tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/subtract-2/deno.json5
-rw-r--r--tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/subtract-2/index.ts6
-rw-r--r--tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/subtract-2/package.json4
-rw-r--r--tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/subtract/deno.json5
-rw-r--r--tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/subtract/index.ts7
-rw-r--r--tests/specs/publish/npm_workspace_jsr_pkg_with_npm_dep/subtract/package.json8
27 files changed, 207 insertions, 43 deletions
diff --git a/Cargo.lock b/Cargo.lock
index fc8714156..4bf79e1fa 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 93e3043d2..d742d01dc 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 d207b81a9..861a63d0c 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<Arc<LspPackageConfig>>,
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 21b46255c..2ca93114d 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 6049ec273..7c47795c4 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 1df3895ef..e0c8e66ff 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<ModuleSpecifier>,
}
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 758db0796..63c773f01 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 ac07c47d1..dc851858e 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<JsxImportSourceConfig>,
maybe_original_import_map: Option<ImportMap>,
) -> 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 79e059ca1..7415a9aeb 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 03956388c..4920b8d6f 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 3a311dcd7..6520666f7 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 1ca11026a..203ac5a99 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 82a49b9fe..eeb455652 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 000000000..92404af87
--- /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 000000000..316503b9c
--- /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 000000000..8819e630d
--- /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 000000000..7d658310b
--- /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 000000000..ecbc84a26
--- /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 000000000..0f6d53f87
--- /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 000000000..89f78c44c
--- /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 000000000..9edadaf67
--- /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 000000000..edae3158b
--- /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 000000000..aa80c50b5
--- /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 000000000..1cf48f264
--- /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 000000000..cb003e374
--- /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 000000000..11f0cf4e8
--- /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 000000000..7d6b0f877
--- /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"
+ }
+}