Skip to content

Commit 304a644

Browse files
fix(config): don't exclude workspace members from deploy file patterns (#33562)
When `deno deploy` runs from a workspace root, `WorkspaceDirectory::to_deploy_config()` calls `append_workspace_members_to_exclude()` which adds all workspace member directories to the exclude list. This causes the deploy CLI to upload zero workspace member files (empty manifest), resulting in errors like: > Working directory at 'packages/backend' not found ## Root Cause The `append_workspace_members_to_exclude()` call was added in [deploy-cli#75](denoland/deploy-cli@92610a9) (shipped in `@deno/deploy@0.0.93`) when file collection moved from TypeScript `walk()` to the Rust `FileCollector` backed by `deno_config::to_deploy_config()`. This exclusion pattern is correct for lint/fmt/test/bench/publish configs (each workspace member handles its own config), but NOT for deploy — the entire workspace needs to be uploaded. ## Fix Remove the `append_workspace_members_to_exclude()` call from `to_deploy_config()` so workspace member files are included in the deploy upload manifest. --------- Signed-off-by: Yusuke Tanaka <wing0920@gmail.com> Co-authored-by: Yusuke Tanaka <yusuktan@maguro.dev>
1 parent f539504 commit 304a644

1 file changed

Lines changed: 255 additions & 1 deletion

File tree

libs/config/workspace/mod.rs

Lines changed: 255 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2423,7 +2423,6 @@ impl WorkspaceDirectory {
24232423
};
24242424
self.exclude_includes_with_member_for_base_for_root(&mut config.files);
24252425
combine_files_config_with_cli_args(&mut config.files, cli_args);
2426-
self.append_workspace_members_to_exclude(&mut config.files);
24272426
Ok(Some(config))
24282427
}
24292428

@@ -6558,4 +6557,259 @@ pub mod test {
65586557
.trim_end_matches('/'),
65596558
)
65606559
}
6560+
6561+
// --- Deploy config tests ---
6562+
6563+
#[test]
6564+
fn test_deploy_config_in_root_does_not_exclude_members() {
6565+
// Regression test: when deploy config is in the workspace root and the
6566+
// user runs `deno deploy` from the root, workspace member directories
6567+
// should NOT be excluded from the file patterns.
6568+
let sys = InMemorySys::default();
6569+
let root_config = root_dir().join("deno.json");
6570+
sys.fs_insert_json(
6571+
root_config.clone(),
6572+
json!({
6573+
"workspace": ["./packages/backend", "./packages/types"],
6574+
"deploy": {
6575+
"org": "myorg",
6576+
"app": "myapp"
6577+
}
6578+
}),
6579+
);
6580+
sys.fs_insert_json(
6581+
root_dir().join("packages/backend/deno.json"),
6582+
json!({
6583+
"version": "0.1.0",
6584+
"exclude": ["coverage/"]
6585+
}),
6586+
);
6587+
sys.fs_insert(
6588+
root_dir().join("packages/backend/main.ts"),
6589+
"Deno.serve(() => new Response('hello'));",
6590+
);
6591+
sys.fs_insert_json(
6592+
root_dir().join("packages/types/deno.json"),
6593+
json!({
6594+
"name": "@myapp/types",
6595+
"exports": "./mod.ts"
6596+
}),
6597+
);
6598+
sys.fs_insert(
6599+
root_dir().join("packages/types/mod.ts"),
6600+
"export type Foo = string;",
6601+
);
6602+
6603+
// Discover from workspace root
6604+
let workspace_dir = workspace_at_start_dir(&sys, &root_dir());
6605+
let deploy_config = workspace_dir
6606+
.to_deploy_config(FilePatterns::new_with_base(workspace_dir.dir_path()))
6607+
.unwrap();
6608+
6609+
assert!(deploy_config.is_some(), "deploy config should be resolved");
6610+
let deploy_config = deploy_config.unwrap();
6611+
assert_eq!(deploy_config.org, "myorg");
6612+
assert_eq!(deploy_config.app, Some("myapp".to_string()));
6613+
6614+
// The critical check: workspace member paths must NOT be excluded
6615+
let backend_dir = root_dir().join("packages/backend");
6616+
let types_dir = root_dir().join("packages/types");
6617+
assert!(
6618+
deploy_config
6619+
.files
6620+
.matches_path(&backend_dir, PathKind::Directory),
6621+
"packages/backend should not be excluded from deploy files"
6622+
);
6623+
assert!(
6624+
deploy_config
6625+
.files
6626+
.matches_path(&types_dir, PathKind::Directory),
6627+
"packages/types should not be excluded from deploy files"
6628+
);
6629+
assert!(
6630+
deploy_config.files.matches_path(
6631+
&root_dir().join("packages/backend/main.ts"),
6632+
PathKind::File,
6633+
),
6634+
"packages/backend/main.ts should be included in deploy files"
6635+
);
6636+
}
6637+
6638+
#[test]
6639+
fn test_deploy_config_in_both_root_and_member() {
6640+
// When both root and member have deploy configs, running from the
6641+
// member directory should resolve the deploy config and not exclude
6642+
// member files.
6643+
let sys = InMemorySys::default();
6644+
sys.fs_insert_json(
6645+
root_dir().join("deno.json"),
6646+
json!({
6647+
"workspace": ["./member"],
6648+
"deploy": {
6649+
"org": "rootorg"
6650+
}
6651+
}),
6652+
);
6653+
sys.fs_insert_json(
6654+
root_dir().join("member/deno.json"),
6655+
json!({
6656+
"deploy": {
6657+
"org": "myorg",
6658+
"app": "myapp"
6659+
}
6660+
}),
6661+
);
6662+
sys.fs_insert(
6663+
root_dir().join("member/main.ts"),
6664+
"Deno.serve(() => new Response('hello'));",
6665+
);
6666+
6667+
let workspace_dir =
6668+
workspace_at_start_dir(&sys, &root_dir().join("member"));
6669+
let deploy_config = workspace_dir
6670+
.to_deploy_config(FilePatterns::new_with_base(workspace_dir.dir_path()))
6671+
.unwrap();
6672+
6673+
assert!(deploy_config.is_some(), "deploy config should be resolved");
6674+
let deploy_config = deploy_config.unwrap();
6675+
// Member's org takes precedence over root's
6676+
assert_eq!(deploy_config.org, "myorg");
6677+
6678+
// Member's own files must match
6679+
assert!(
6680+
deploy_config
6681+
.files
6682+
.matches_path(&root_dir().join("member/main.ts"), PathKind::File,),
6683+
"member/main.ts should be included"
6684+
);
6685+
}
6686+
6687+
#[test]
6688+
fn test_deploy_config_root_only_no_member_deploy() {
6689+
// deploy:{org,app} in root, NO deploy in any member.
6690+
// When running from root, to_deploy_config should return Some.
6691+
let sys = InMemorySys::default();
6692+
sys.fs_insert_json(
6693+
root_dir().join("deno.json"),
6694+
json!({
6695+
"workspace": [
6696+
"./packages/backend",
6697+
"./packages/types",
6698+
"./packages/schema"
6699+
],
6700+
"deploy": {
6701+
"org": "sfdevtools",
6702+
"app": "prod"
6703+
}
6704+
}),
6705+
);
6706+
sys.fs_insert_json(
6707+
root_dir().join("packages/backend/deno.json"),
6708+
json!({ "version": "0.1.0" }),
6709+
);
6710+
sys.fs_insert(
6711+
root_dir().join("packages/backend/main.ts"),
6712+
"Deno.serve(() => new Response('hello'));",
6713+
);
6714+
sys.fs_insert_json(
6715+
root_dir().join("packages/types/deno.json"),
6716+
json!({ "name": "@myapp/types", "exports": "./mod.ts" }),
6717+
);
6718+
sys.fs_insert(
6719+
root_dir().join("packages/types/mod.ts"),
6720+
"export type Foo = string;",
6721+
);
6722+
sys.fs_insert_json(
6723+
root_dir().join("packages/schema/deno.json"),
6724+
json!({ "name": "@myapp/schema", "exports": "./mod.ts" }),
6725+
);
6726+
sys.fs_insert(
6727+
root_dir().join("packages/schema/mod.ts"),
6728+
"export const VERSION = 1;",
6729+
);
6730+
6731+
let workspace_dir = workspace_at_start_dir(&sys, &root_dir());
6732+
let deploy_config = workspace_dir
6733+
.to_deploy_config(FilePatterns::new_with_base(workspace_dir.dir_path()))
6734+
.unwrap();
6735+
6736+
assert!(
6737+
deploy_config.is_some(),
6738+
"deploy config should be resolved when only root has deploy field"
6739+
);
6740+
let deploy_config = deploy_config.unwrap();
6741+
assert_eq!(deploy_config.org, "sfdevtools");
6742+
6743+
// All workspace member directories must be accessible
6744+
for member in &["packages/backend", "packages/types", "packages/schema"] {
6745+
let member_dir = root_dir().join(member);
6746+
assert!(
6747+
deploy_config
6748+
.files
6749+
.matches_path(&member_dir, PathKind::Directory),
6750+
"{member} should not be excluded from deploy files"
6751+
);
6752+
}
6753+
// Entrypoint must be accessible
6754+
assert!(
6755+
deploy_config.files.matches_path(
6756+
&root_dir().join("packages/backend/main.ts"),
6757+
PathKind::File,
6758+
),
6759+
"packages/backend/main.ts must be included"
6760+
);
6761+
}
6762+
6763+
#[test]
6764+
fn test_deploy_config_with_exclude() {
6765+
// Deploy config with explicit exclude should respect it but not
6766+
// additionally exclude workspace members.
6767+
let sys = InMemorySys::default();
6768+
sys.fs_insert_json(
6769+
root_dir().join("deno.json"),
6770+
json!({
6771+
"workspace": ["./packages/backend", "./packages/types"],
6772+
"exclude": ["docs/"],
6773+
"deploy": {
6774+
"org": "myorg",
6775+
"app": "myapp"
6776+
}
6777+
}),
6778+
);
6779+
sys
6780+
.fs_insert_json(root_dir().join("packages/backend/deno.json"), json!({}));
6781+
sys.fs_insert(
6782+
root_dir().join("packages/backend/main.ts"),
6783+
"Deno.serve(() => new Response('hello'));",
6784+
);
6785+
sys.fs_insert_json(root_dir().join("packages/types/deno.json"), json!({}));
6786+
sys.fs_insert(
6787+
root_dir().join("packages/types/mod.ts"),
6788+
"export type Foo = string;",
6789+
);
6790+
6791+
let workspace_dir = workspace_at_start_dir(&sys, &root_dir());
6792+
let deploy_config = workspace_dir
6793+
.to_deploy_config(FilePatterns::new_with_base(workspace_dir.dir_path()))
6794+
.unwrap();
6795+
6796+
assert!(deploy_config.is_some());
6797+
let deploy_config = deploy_config.unwrap();
6798+
6799+
// Workspace members should NOT be excluded
6800+
assert!(
6801+
deploy_config.files.matches_path(
6802+
&root_dir().join("packages/backend/main.ts"),
6803+
PathKind::File,
6804+
),
6805+
"workspace member files should not be excluded"
6806+
);
6807+
// docs/ should be excluded (from top-level exclude)
6808+
assert!(
6809+
!deploy_config
6810+
.files
6811+
.matches_path(&root_dir().join("docs/readme.md"), PathKind::File),
6812+
"docs/ should be excluded"
6813+
);
6814+
}
65616815
}

0 commit comments

Comments
 (0)