Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,20 @@ export const licenseFile: Resolution = async inputs => {
maxDepth: 1,
});

const filteredLicenseFiles = licenseFiles.filter(file => !extensionDenyList.includes(extname(file)));
// glob({ nocase: true }) returns matches using the pattern's case on
// case-insensitive filesystems (e.g. "license" on macOS) but the on-disk
// case elsewhere (e.g. "LICENSE" on Linux), and in filesystem order either
// way. Sort case-insensitively (locale-independently, for determinism) so
// the chosen file and the warning are stable across platforms; the base
// name (e.g. "license") then sorts ahead of decorated variants
// (e.g. "license-3rdparty.csv"), which is usually the real license.
const filteredLicenseFiles = licenseFiles
.filter(file => !extensionDenyList.includes(extname(file)))
.sort((a, b) => {
const lowerA = a.toLowerCase();
const lowerB = b.toLowerCase();
return lowerA < lowerB ? -1 : lowerA > lowerB ? 1 : 0;
});

if (filteredLicenseFiles.length === 0) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,32 @@ describe("licenseFile", () => {
expect(result).toBe("license contents");
});

it("should pick the base license file over decorated variants regardless of glob's order or casing", async () => {
// glob({ nocase: true }) returns matches using the pattern's case on
// case-insensitive filesystems (macOS yields "license") but the on-disk
// case elsewhere (Linux yields "LICENSE"), in filesystem order either way.
// The real license must win over a decorated variant like
// "LICENSE-3rdparty.csv" on both, so the output is identical cross-platform.
const upperLicense = "/some/directory/LICENSE";
const lowerLicense = "/some/directory/license";
const thirdParty = "/some/directory/LICENSE-3rdparty.csv";

when(mockedReadFile).calledWith(upperLicense, { encoding: "utf-8" }).thenResolve("license contents");
when(mockedReadFile).calledWith(lowerLicense, { encoding: "utf-8" }).thenResolve("license contents");
when(mockedReadFile).calledWith(thirdParty, { encoding: "utf-8" }).thenResolve("third party contents");

// Case-sensitive filesystem (e.g. Linux): glob yields the on-disk "LICENSE"
mockedGlob.mockResolvedValueOnce([thirdParty, upperLicense]);
const caseSensitive = await licenseFile(resolutionInputs);

// Case-insensitive filesystem (e.g. macOS): glob yields the pattern's "license"
mockedGlob.mockResolvedValueOnce([thirdParty, lowerLicense]);
const caseInsensitive = await licenseFile(resolutionInputs);

expect(caseSensitive).toBe("license contents");
expect(caseInsensitive).toBe("license contents");
});

it.each(
extensionDenyList,
)("should return null if all license files are in the extension deny list", async extension => {
Expand Down