Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: support publishing jsr packages in an npm workspace #77

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 92 additions & 14 deletions src/workspace/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -674,27 +674,25 @@ impl Workspace {

pub fn jsr_packages_for_publish(self: &WorkspaceRc) -> Vec<JsrPackageConfig> {
let ctx = self.resolve_start_ctx();
let Some(config) = &ctx.deno_json else {
return Vec::new();
};
let deno_json = &config.member;
// only publish the current folder if it's a package
if let Some(package_config) = ctx.maybe_package_config() {
return vec![package_config];
}
if let Some(pkg_json) = &ctx.pkg_json {
let ctx_dir_path = ctx.dir_url.to_file_path().unwrap();
// don't publish anything if in a package.json only directory within
// a workspace
if pkg_json.member.dir_path().starts_with(deno_json.dir_path())
&& deno_json.dir_path() != pkg_json.member.dir_path()
if pkg_json.member.dir_path().starts_with(&ctx_dir_path)
&& ctx_dir_path != pkg_json.member.dir_path()
{
return Vec::new();
}
}
if deno_json.dir_path() == self.root_dir.to_file_path().unwrap()
&& !(deno_json.is_workspace() && deno_json.is_package())
{
return self.jsr_packages();
}
match ctx.maybe_package_config() {
Some(pkg) => vec![pkg],
None => Vec::new(),
if ctx.dir_url == self.root_dir {
self.jsr_packages()
} else {
// nothing to publish
Vec::new()
}
}

Expand Down Expand Up @@ -3027,6 +3025,86 @@ mod test {
assert_eq!(names, vec!["@scope/pkg"]);
}

#[test]
fn test_packages_for_publish_npm_workspace() {
let mut fs = TestFileSystem::default();
fs.insert_json(
root_dir().join("package.json"),
json!({
"workspaces": ["./a", "./b", "./c", "./d"]
}),
);
fs.insert_json(root_dir().join("a/package.json"), json!({}));
fs.insert_json(
root_dir().join("a/deno.json"),
json!({
"name": "@scope/a",
"version": "1.0.0",
"exports": "./main.ts",
}),
);
fs.insert_json(root_dir().join("b/package.json"), json!({}));
fs.insert_json(
root_dir().join("b/deno.json"),
json!({
"name": "@scope/b",
"version": "1.0.0",
"exports": "./main.ts",
}),
);
fs.insert_json(root_dir().join("c/package.json"), json!({}));
fs.insert_json(
root_dir().join("c/deno.json"),
// not a package
json!({}),
);
fs.insert_json(
root_dir().join("d/package.json"),
json!({
"name": "pkg",
"version": "1.0.0",
}),
);
// root
{
let workspace = workspace_at_start_dir(&fs, &root_dir());
assert_eq!(workspace.diagnostics(), vec![]);
let jsr_pkgs = workspace.jsr_packages_for_publish();
let names = jsr_pkgs.iter().map(|p| p.name.as_str()).collect::<Vec<_>>();
assert_eq!(names, vec!["@scope/a", "@scope/b"]);
}
// member
{
let workspace = workspace_at_start_dir(&fs, &root_dir().join("a"));
assert_eq!(workspace.diagnostics(), vec![]);
let jsr_pkgs = workspace.jsr_packages_for_publish();
let names = jsr_pkgs.iter().map(|p| p.name.as_str()).collect::<Vec<_>>();
assert_eq!(names, vec!["@scope/a"]);
}
// member, not a package
{
let workspace = workspace_at_start_dir(&fs, &root_dir().join("c"));
assert_eq!(workspace.diagnostics(), vec![]);
let jsr_pkgs = workspace.jsr_packages_for_publish();
assert!(jsr_pkgs.is_empty());
}
// package.json
{
let workspace = workspace_at_start_dir(&fs, &root_dir().join("d"));
assert_eq!(workspace.diagnostics(), vec![]);
let jsr_pkgs = workspace.jsr_packages_for_publish();
assert!(jsr_pkgs.is_empty());
assert_eq!(
workspace
.npm_packages()
.into_iter()
.map(|p| p.pkg_json.dir_path().to_path_buf())
.collect::<Vec<_>>(),
vec![root_dir().join("d")]
);
}
}

#[test]
fn test_no_auto_discovery_node_modules_dir() {
let mut fs = TestFileSystem::default();
Expand Down
106 changes: 74 additions & 32 deletions src/workspace/resolver.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright 2018-2024 the Deno authors. MIT license.

use std::borrow::Cow;
use std::collections::BTreeMap;
use std::future::Future;
use std::path::Path;
Expand Down Expand Up @@ -190,40 +191,40 @@ impl WorkspaceResolver {
(base_url, import_map)
}
None => {
let Some(config) = root_folder.deno_json.as_ref() else {
if !deno_jsons.iter().any(|p| p.is_package())
&& !deno_jsons.iter().any(|c| {
c.json.import_map.is_some()
|| c.json.scopes.is_some()
|| c.json.imports.is_some()
})
{
// no configs have an import map and none are a package, so exit
return Ok(None);
}

let config_specified_import_map = match root_folder.deno_json.as_ref()
{
Some(deno_json) => deno_json
.to_import_map_value(fetch_text)
.await
.map_err(|source| WorkspaceResolverCreateError::ImportMapFetch {
referrer: deno_json.specifier.clone(),
source,
})?
.unwrap_or_else(|| {
(
Cow::Borrowed(&deno_json.specifier),
serde_json::Value::Object(Default::default()),
)
}),
None => (
Cow::Owned(workspace.root_dir.join("deno.json").unwrap()),
serde_json::Value::Object(Default::default()),
),
};
let config_specified_import_map = config
.to_import_map_value(fetch_text)
.await
.map_err(|source| WorkspaceResolverCreateError::ImportMapFetch {
referrer: config.specifier.clone(),
source,
})?;
let base_import_map_config = match config_specified_import_map {
Some((base_url, import_map_value)) => {
import_map::ext::ImportMapConfig {
base_url: base_url.into_owned(),
import_map_value,
}
}
None => {
if !deno_jsons.iter().any(|p| p.is_package())
&& !deno_jsons.iter().any(|c| {
c.json.import_map.is_some()
|| c.json.scopes.is_some()
|| c.json.imports.is_some()
})
{
// no configs have an import map and none are a package, so exit
return Ok(None);
}

import_map::ext::ImportMapConfig {
base_url: config.specifier.clone(),
import_map_value: serde_json::Value::Object(Default::default()),
}
}
let base_import_map_config = import_map::ext::ImportMapConfig {
base_url: config_specified_import_map.0.into_owned(),
import_map_value: config_specified_import_map.1,
};
let child_import_map_configs = deno_jsons
.iter()
Expand Down Expand Up @@ -703,6 +704,47 @@ mod test {
}
}

#[tokio::test]
async fn resolve_workspace_pkg_json_workspace_deno_json_import_map() {
let mut fs = TestFileSystem::default();
fs.insert_json(
root_dir().join("package.json"),
json!({
"workspaces": ["*"]
}),
);
fs.insert_json(
root_dir().join("a/package.json"),
json!({
"name": "@scope/a",
"version": "1.0.0",
}),
);
fs.insert_json(
root_dir().join("a/deno.json"),
json!({
"name": "@scope/jsr-pkg",
"version": "1.0.0",
"exports": "./mod.ts"
}),
);

let workspace = workspace_at_start_dir(&fs, &root_dir());
let resolver = create_resolver(&workspace).await;
let resolution = resolver
.resolve(
"@scope/jsr-pkg",
&Url::from_file_path(root_dir().join("b.ts")).unwrap(),
)
.unwrap();
match resolution {
MappedResolution::ImportMap(specifier) => {
assert_eq!(specifier, Url::parse("jsr:@scope/jsr-pkg@^1.0.0").unwrap());
}
_ => unreachable!(),
}
}

#[tokio::test]
async fn specified_import_map() {
let mut fs = TestFileSystem::default();
Expand Down