Skip to content

Commit c878f14

Browse files
authored
fix(config): warn instead of erroring when start dir is not a workspace member (#34458)
When a parent ``deno.json`` declares a ``workspace`` property (including an empty ``workspace: []``) and Deno is invoked from a child directory that is not listed as a member, discovery previously errored with "Config file must be a member of the workspace." That blocks legitimate setups like a nested ``package.json``-only folder sitting under a Deno workspace root, even when the user's intent is simply to use the parent config. Instead, discard the parent workspace, resolve the start directory as a standalone workspace, and emit a warning so the user knows the parent config was ignored. This mirrors the existing silent fallback for npm workspaces and matches the resolution discussed on the issue. Fixes #30672
1 parent 56723af commit c878f14

7 files changed

Lines changed: 89 additions & 34 deletions

File tree

libs/config/workspace/discovery.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -583,16 +583,25 @@ fn handle_workspace_with_members<TSys: FsRead + FsMetadata + FsReadDir>(
583583
.root_deno_json()
584584
.map(|d| d.json.workspace.is_some())
585585
.unwrap_or(false);
586-
// if the root was an npm workspace that doesn't have the start config
587-
// as a member then only resolve the start config
588-
if !is_root_deno_json_workspace
589-
&& let Some(first_config_folder) = &first_config_folder_url
586+
// If the start config folder is not a member of the discovered workspace,
587+
// discard the parent workspace and resolve just the start config standalone.
588+
// For npm workspaces this is silent backwards-compatible behavior; for deno
589+
// workspaces with an explicit "workspace" property we warn so the user knows
590+
// the parent config was ignored.
591+
if let Some(first_config_folder) = &first_config_folder_url
590592
&& !root_workspace
591593
.config_folders
592594
.contains_key(*first_config_folder)
593595
&& let Some(config_folder) =
594596
found_config_folders.remove(first_config_folder)
595597
{
598+
if is_root_deno_json_workspace {
599+
log::warn!(
600+
"Warning: config file {} is not a member of the workspace at {}. Ignoring the parent workspace config.",
601+
config_folder_config_specifier(&config_folder),
602+
root_workspace.root_dir_url(),
603+
);
604+
}
596605
let maybe_vendor_dir = resolve_vendor_dir(
597606
config_folder.deno_json().map(|d| d.as_ref()),
598607
opts.maybe_vendor_override.as_ref(),

libs/config/workspace/mod.rs

Lines changed: 57 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4958,23 +4958,25 @@ pub mod test {
49584958

49594959
#[test]
49604960
fn test_deno_member_not_referenced_in_deno_workspace() {
4961-
fn assert_err(err: &WorkspaceDiscoverError, config_file_path: &Path) {
4962-
match err.as_kind() {
4963-
WorkspaceDiscoverErrorKind::ConfigNotWorkspaceMember {
4964-
workspace_url,
4965-
config_url,
4966-
} => {
4967-
assert_eq!(
4968-
workspace_url,
4969-
&url_from_directory_path(&root_dir()).unwrap()
4970-
);
4971-
assert_eq!(
4972-
config_url,
4973-
&url_from_file_path(config_file_path).unwrap()
4974-
);
4975-
}
4976-
_ => unreachable!(),
4977-
}
4961+
// A start directory that has a deno.json but is not a member of the
4962+
// parent deno workspace falls back to a standalone workspace at the start
4963+
// directory (a warning is logged about the ignored parent workspace).
4964+
fn assert_standalone(
4965+
workspace_dir: &WorkspaceDirectoryRc,
4966+
config_file_path: &Path,
4967+
) {
4968+
assert_eq!(
4969+
workspace_dir.workspace.root_dir_path(),
4970+
config_file_path.parent().unwrap(),
4971+
);
4972+
assert_eq!(
4973+
workspace_dir
4974+
.workspace
4975+
.deno_jsons()
4976+
.map(|c| deno_path_util::url_to_file_path(&c.specifier).unwrap())
4977+
.collect::<Vec<_>>(),
4978+
vec![config_file_path.to_path_buf()],
4979+
);
49784980
}
49794981

49804982
for file_name in ["deno.json", "deno.jsonc"] {
@@ -4988,20 +4990,21 @@ pub mod test {
49884990
);
49894991
sys.fs_insert_json(root_dir().join("member-a/deno.json"), json!({}));
49904992
sys.fs_insert_json(config_file_path.clone(), json!({}));
4991-
let err = workspace_at_start_dir_err(&sys, &root_dir().join("member-b"));
4992-
assert_err(&err, &config_file_path);
4993+
let workspace_dir =
4994+
workspace_at_start_dir(&sys, &root_dir().join("member-b"));
4995+
assert_standalone(&workspace_dir, &config_file_path);
49934996

49944997
// try for when the config file is specified as well
4995-
let err = WorkspaceDirectory::discover(
4998+
let workspace_dir = WorkspaceDirectory::discover(
49964999
&sys,
49975000
WorkspaceDiscoverStart::ConfigFile(&config_file_path),
49985001
&WorkspaceDiscoverOptions {
49995002
discover_pkg_json: true,
50005003
..Default::default()
50015004
},
50025005
)
5003-
.unwrap_err();
5004-
assert_err(&err, &config_file_path);
5006+
.unwrap();
5007+
assert_standalone(&workspace_dir, &config_file_path);
50055008
}
50065009
}
50075010

@@ -5085,16 +5088,40 @@ pub mod test {
50855088
);
50865089
sys.fs_insert_json(root_dir().join("member/deno.json"), json!({}));
50875090
sys.fs_insert_json(root_dir().join("package/package.json"), json!({}));
5088-
// npm package needs to be a member of the deno workspace
5089-
let err = workspace_at_start_dir_err(&sys, &root_dir().join("package"));
5091+
// The start folder is not referenced by the parent deno workspace, so the
5092+
// parent workspace is discarded and the start folder is resolved on its
5093+
// own (a warning is logged for the deno workspace case).
5094+
let workspace_dir =
5095+
workspace_at_start_dir(&sys, &root_dir().join("package"));
50905096
assert_eq!(
5091-
err.to_string(),
5092-
normalize_err_text(
5093-
"Config file must be a member of the workspace.
5094-
Config: [ROOT_DIR_URL]/package/package.json
5095-
Workspace: [ROOT_DIR_URL]/"
5096-
)
5097+
workspace_dir.workspace.root_dir_path(),
5098+
root_dir().join("package"),
50975099
);
5100+
assert_eq!(workspace_dir.workspace.deno_jsons().count(), 0);
5101+
assert_eq!(workspace_dir.workspace.package_jsons().count(), 1);
5102+
}
5103+
5104+
#[test]
5105+
fn test_npm_package_under_deno_workspace_with_empty_members() {
5106+
// Exact repro from #30672: parent deno.json has an empty `workspace` array
5107+
// and the start folder contains only a package.json. The parent workspace
5108+
// should be discarded and the start folder resolved standalone.
5109+
let sys = InMemorySys::default();
5110+
sys.fs_insert_json(
5111+
root_dir().join("deno.json"),
5112+
json!({
5113+
"workspace": []
5114+
}),
5115+
);
5116+
sys.fs_insert_json(root_dir().join("package/package.json"), json!({}));
5117+
let workspace_dir =
5118+
workspace_at_start_dir(&sys, &root_dir().join("package"));
5119+
assert_eq!(
5120+
workspace_dir.workspace.root_dir_path(),
5121+
root_dir().join("package"),
5122+
);
5123+
assert_eq!(workspace_dir.workspace.deno_jsons().count(), 0);
5124+
assert_eq!(workspace_dir.workspace.package_jsons().count(), 1);
50985125
}
50995126

51005127
#[test]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"tests": {
3+
"child_dir_not_in_workspace": {
4+
"cwd": "child",
5+
"args": "run main.ts",
6+
"output": "child/main.out"
7+
}
8+
}
9+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Warning: config file [WILDLINE] is not a member of the workspace at [WILDLINE]. Ignoring the parent workspace config.
2+
hello from child
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log("hello from child");
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "child",
3+
"version": "1.0.0"
4+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"workspace": []
3+
}

0 commit comments

Comments
 (0)