Skip to content

Commit 16ef616

Browse files
committed
feat(config): allow other .json files when explicitly set
Closes #333
1 parent f9cd4ab commit 16ef616

7 files changed

Lines changed: 86 additions & 7 deletions

File tree

src/cli.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -713,10 +713,10 @@ fn additional_help() -> String {
713713
}
714714

715715
fn validate_source(value: &str) -> Result<String, String> {
716-
if value.ends_with("package.json") {
716+
if value.ends_with(".json") {
717717
Ok(value.to_string())
718718
} else {
719-
Err("must end with 'package.json'".to_string())
719+
Err("must end with '.json'".to_string())
720720
}
721721
}
722722

src/disk.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ impl DiskIo for LiveDiskIo {
467467
None
468468
}
469469
})
470-
.filter(|entry| entry.file_name() == "package.json")
470+
.filter(|entry| entry.file_type().is_some_and(|t| t.is_file()) && entry.file_name().to_string_lossy().ends_with(".json"))
471471
.map(|entry| entry.into_path())
472472
.collect()
473473
}

src/source_patterns.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ fn normalise_patterns(patterns: Vec<String>) -> Vec<String> {
101101
/// Normalize a source pattern by:
102102
/// 1. Preserving negation prefix (`!`) through normalization
103103
/// 2. Converting Windows backslashes to forward slashes for glob compatibility
104-
/// 3. Ensuring pattern ends with /package.json
104+
/// 3. Ensuring pattern ends with /package.json when no explicit `.json` filename was given
105105
/// 4. Anchoring slashless patterns to the workspace root with a leading `/` so gitignore basename rules don't make them match at any depth
106106
///
107107
/// Examples:
@@ -110,17 +110,23 @@ fn normalise_patterns(patterns: Vec<String>) -> Vec<String> {
110110
/// - "package.json" -> "/package.json"
111111
/// - "apps\\*/package.json" -> "apps/*/package.json"
112112
/// - "!apps/test2" -> "!apps/test2/package.json"
113+
/// - "packages/*/package.public.json" -> "packages/*/package.public.json"
113114
pub fn normalise_pattern(mut pattern: String) -> String {
114115
let negated = pattern.starts_with('!');
115116
if negated {
116117
pattern.remove(0);
117118
}
118119
let mut normalized = pattern.replace('\\', "/");
119-
if !normalized.contains("package.json") {
120+
if !basename_ends_with_json(&normalized) {
120121
normalized = format!("{normalized}/package.json");
121122
}
122123
if !normalized.contains('/') {
123124
normalized = format!("/{normalized}");
124125
}
125126
if negated { format!("!{normalized}") } else { normalized }
126127
}
128+
129+
fn basename_ends_with_json(pattern: &str) -> bool {
130+
let basename = pattern.rsplit('/').next().unwrap_or(pattern);
131+
basename.ends_with(".json")
132+
}

src/source_patterns_test.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@ fn normalizes_backslashes_to_forward_slashes() {
3434
("!apps/test2/package.json", "!apps/test2/package.json"),
3535
("!projects\\apps\\*", "!projects/apps/*/package.json"),
3636
];
37+
let non_standard_manifest_names = [
38+
("packages/*/package.public.json", "packages/*/package.public.json"),
39+
("packages/*/package.private.json", "packages/*/package.private.json"),
40+
("packages/foo/manifest.json", "packages/foo/manifest.json"),
41+
("package.public.json", "/package.public.json"),
42+
("packages\\*\\package.public.json", "packages/*/package.public.json"),
43+
("packages/*/*.json", "packages/*/*.json"),
44+
("!packages/foo/package.public.json", "!packages/foo/package.public.json"),
45+
("!packages/*/*.json", "!packages/*/*.json"),
46+
];
3747

3848
let cases = windows_backslashes
3949
.iter()
@@ -43,7 +53,8 @@ fn normalizes_backslashes_to_forward_slashes() {
4353
.chain(forward_slashes_with_package_json.iter())
4454
.chain(bare_package_json.iter())
4555
.chain(glob_patterns.iter())
46-
.chain(negated_globs.iter());
56+
.chain(negated_globs.iter())
57+
.chain(non_standard_manifest_names.iter());
4758

4859
for (input, expected) in cases {
4960
let result = normalise_pattern(input.to_string());

src/test/builder.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ pub struct TestBuilder {
4646
dependency_groups: Vec<Value>,
4747
package_manager: Option<PackageManager>,
4848
packages: Vec<Value>,
49+
manifests_at: Vec<(String, Value)>,
4950
pnpm_yaml: Option<String>,
5051
bun_root: Option<Value>,
5152
registry_updates: Option<Value>,
@@ -67,6 +68,7 @@ impl TestBuilder {
6768
dependency_groups: vec![],
6869
package_manager: None,
6970
packages: vec![],
71+
manifests_at: vec![],
7072
pnpm_yaml: None,
7173
bun_root: None,
7274
registry_updates: None,
@@ -163,6 +165,14 @@ impl TestBuilder {
163165
self
164166
}
165167

168+
/// Add a manifest file at an arbitrary path (e.g. `packages/foo/package.public.json`).
169+
/// Goes through MockDisk like `with_package` but the caller controls the filename
170+
/// so non-standard manifests (per issue #333) can be exercised.
171+
pub fn with_manifest_at(mut self, path: &str, json: Value) -> Self {
172+
self.manifests_at.push((path.to_string(), json));
173+
self
174+
}
175+
166176
pub fn with_version_group(mut self, group: Value) -> Self {
167177
self.version_groups.push(group);
168178
self
@@ -275,6 +285,11 @@ impl TestBuilder {
275285
disk.add_json(&path, pkg);
276286
}
277287

288+
// Add manifests at caller-specified paths (e.g. package.public.json).
289+
for (path, json) in &self.manifests_at {
290+
disk.add_json(path, json);
291+
}
292+
278293
// Synthetic Bun root + bun.lock to trigger PM=Bun + catalog discovery.
279294
if let Some(ref root) = self.bun_root {
280295
disk.add_json("package.json", root);
@@ -425,6 +440,15 @@ impl TestBuilder {
425440
dirty: false,
426441
});
427442
}
443+
for (path, json) in &self.manifests_at {
444+
let raw = serde_json::to_string_pretty(json).unwrap_or_default();
445+
package_json_files.push(File {
446+
filepath: PathBuf::from(format!("/{path}")),
447+
formatting: detect_formatting(&raw),
448+
contents: json.clone(),
449+
dirty: false,
450+
});
451+
}
428452
let pnpm_workspace = self
429453
.pnpm_yaml
430454
.as_ref()

src/test/builder_test.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,41 @@ async fn test_builder_with_update_target() {
108108
let foo_instance = ctx.instances.iter().find(|i| i.descriptor.internal_name == "foo").unwrap();
109109
assert_eq!(foo_instance.expected_specifier.borrow().as_ref().unwrap().get_raw(), "1.1.0");
110110
}
111+
112+
// Issue #333: source patterns with non-standard filenames (e.g. package.public.json)
113+
// must be discovered when explicitly listed in `source`. The default rule of
114+
// appending `/package.json` still applies when no `.json` suffix is given.
115+
#[tokio::test]
116+
async fn test_builder_with_non_standard_manifest_filename() {
117+
let ctx = TestBuilder::new()
118+
.with_package(json!({
119+
"name": "package-a",
120+
"version": "1.0.0",
121+
"dependencies": {"foo": "1.0.0"}
122+
}))
123+
.with_manifest_at(
124+
"packages/package-a/package.public.json",
125+
json!({
126+
"name": "package-a-public",
127+
"version": "2.0.0",
128+
"dependencies": {"bar": "1.0.0"}
129+
}),
130+
)
131+
.with_config(json!({
132+
"source": ["packages/*/package.json", "packages/*/package.public.json"]
133+
}))
134+
.run()
135+
.await;
136+
137+
let dep_names: Vec<&str> = ctx.instances.iter().map(|i| i.descriptor.internal_name.as_str()).collect();
138+
assert!(
139+
dep_names.contains(&"package-a"),
140+
"package-a self-version missing; got {dep_names:?}"
141+
);
142+
assert!(dep_names.contains(&"foo"), "foo dependency missing; got {dep_names:?}");
143+
assert!(
144+
dep_names.contains(&"package-a-public"),
145+
"package-a-public self-version missing; got {dep_names:?}"
146+
);
147+
assert!(dep_names.contains(&"bar"), "bar dependency missing; got {dep_names:?}");
148+
}

src/test/mock_disk.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ impl DiskIo for MockDiskIo {
178178
self
179179
.files
180180
.keys()
181-
.filter(|path| path.file_name().is_some_and(|n| n == "package.json"))
181+
.filter(|path| path.file_name().and_then(|n| n.to_str()).is_some_and(|n| n.ends_with(".json")))
182182
.filter(|path| {
183183
let rel = path.strip_prefix(root).unwrap_or(path);
184184
overrides.matched(rel, false).is_whitelist()

0 commit comments

Comments
 (0)