Skip to content

Commit e12efca

Browse files
committed
fix(cli): error on ambiguous case-variant document matches
On case-sensitive filesystems, .spm.json and .SPM.json are different files. resolveInput now errors when multiple entries match the same exact name case-insensitively, consistent with glob-tier behaviour.
1 parent 3fc01be commit e12efca

2 files changed

Lines changed: 44 additions & 5 deletions

File tree

src/cli/shared.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,16 @@ export function resolveInput(input?: string, cwd?: string): string {
5555
const entries = readdirSync(dir);
5656

5757
for (const name of exactNames) {
58-
const found = entries.find((e) => e.toLowerCase() === name);
59-
if (found) {
60-
const candidate = join(dir, found);
61-
if (name.endsWith(".spm") || name.endsWith(".sysprom")) {
58+
const isDirSuffix = name.endsWith(".spm") || name.endsWith(".sysprom");
59+
const found = entries.filter((e) => e.toLowerCase() === name);
60+
if (found.length > 1) {
61+
throw new Error(
62+
`Multiple SysProM documents found: ${found.join(", ")}. Specify one explicitly.`,
63+
);
64+
}
65+
if (found.length === 1) {
66+
const candidate = join(dir, found[0]);
67+
if (isDirSuffix) {
6268
try {
6369
if (statSync(candidate).isDirectory()) return candidate;
6470
} catch {

tests/resolve-input.unit.test.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, it, beforeEach, afterEach } from "node:test";
22
import assert from "node:assert/strict";
3-
import { mkdirSync, writeFileSync, rmSync } from "node:fs";
3+
import { mkdirSync, writeFileSync, rmSync, existsSync } from "node:fs";
44
import { join } from "node:path";
55
import { resolveInput } from "../src/cli/shared.js";
66

@@ -169,4 +169,37 @@ describe("resolveInput", () => {
169169
mkdirSync(join(TMP, ".SYSPROM"));
170170
assert.equal(resolveInput(undefined, TMP), join(TMP, ".SYSPROM"));
171171
});
172+
173+
// Ambiguous case variants (only testable on case-sensitive filesystems)
174+
const caseSensitiveFs = (() => {
175+
const probeDir = join(import.meta.dirname, ".tmp-case-probe");
176+
mkdirSync(probeDir, { recursive: true });
177+
const probe = join(probeDir, "__CaSe__");
178+
writeFileSync(probe, "");
179+
const result = !existsSync(join(probeDir, "__case__"));
180+
rmSync(probeDir, { recursive: true, force: true });
181+
return result;
182+
})();
183+
184+
it("errors on case-variant exact matches (.spm.json vs .SPM.json)", {
185+
skip: !caseSensitiveFs && "case-insensitive filesystem",
186+
}, () => {
187+
writeFileSync(join(TMP, ".spm.json"), "{}");
188+
writeFileSync(join(TMP, ".SPM.json"), "{}");
189+
assert.throws(
190+
() => resolveInput(undefined, TMP),
191+
/Multiple SysProM documents found/,
192+
);
193+
});
194+
195+
it("errors on case-variant glob matches (a.spm.json vs A.SPM.JSON)", {
196+
skip: !caseSensitiveFs && "case-insensitive filesystem",
197+
}, () => {
198+
writeFileSync(join(TMP, "a.spm.json"), "{}");
199+
writeFileSync(join(TMP, "A.SPM.JSON"), "{}");
200+
assert.throws(
201+
() => resolveInput(undefined, TMP),
202+
/Multiple SysProM documents found/,
203+
);
204+
});
172205
});

0 commit comments

Comments
 (0)