Skip to content
Merged
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
7 changes: 7 additions & 0 deletions cli/git-user/git-user.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ describe("getLocalGitUser", () => {
expect(getLocalGitUser()).toEqual({ name: "tomhagen" });
});

it("returns an user with a trimmed username", () => {
(execSync as jest.Mock).mockReturnValueOnce("tomhagen ");
expect(getLocalGitUser()).toEqual({ name: "tomhagen" });
(execSync as jest.Mock).mockReturnValueOnce("tomhagen\n\n");
expect(getLocalGitUser()).toEqual({ name: "tomhagen" });
});

it("throws an error if execSync fails", () => {
(execSync as jest.Mock).mockImplementationOnce(() => {
throw new Error();
Expand Down
2 changes: 1 addition & 1 deletion cli/git-user/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export function getLocalGitUser(): GitUser {
if (!name) {
throw new Error();
}
return { name };
return { name: name.trim().replaceAll("\n", "") };
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (_: unknown) {
throw new Error(getRandomMessage(MessageType.NoGitConfig));
Expand Down
2 changes: 1 addition & 1 deletion scripts/init/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function informFileCreated(
`- The following crews were detected:\n${crews
.map((crew) => ` - ${crew}`)
.join("\n")}
Please specify their members in the codefather config for CLI enforcement.`
\n⚙️ Please specify their members in the codefather config for CLI enforcement.\n`
);
}
}
Expand Down
65 changes: 65 additions & 0 deletions shared/file-matcher/file-matcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,69 @@ describe("matchFilesAgainstRule", () => {
const matched = matchFilesAgainstRule([file], rule.match, configRoot);
expect(matched).toContain(file);
});
test("matches rule with leading slash", () => {
const result = matchFilesAgainstRule(updatedFiles, ["/src/core/**"], root);
expect(result).toEqual([resolve("project/src/core/index.ts")]);
});
test("matches rule with trailing slash", () => {
const result = matchFilesAgainstRule(
updatedFiles,
["src/core/"], // Should auto-expand to src/core/**
root
);
expect(result).toEqual([resolve("project/src/core/index.ts")]);
});
test("matches rule with whitespace", () => {
const result = matchFilesAgainstRule(
updatedFiles,
[" src/core/** "],
root
);
expect(result).toEqual([resolve("project/src/core/index.ts")]);
});
test("matches dotfiles", () => {
const result = matchFilesAgainstRule(updatedFiles, [".env"], root);
expect(result).toEqual([resolve("project/.env")]);
});
test("matches deeply nested file", () => {
const result = matchFilesAgainstRule(
updatedFiles,
["src/models/searches/**"],
root
);
expect(result).toEqual([resolve("project/src/models/searches/utils.ts")]);
});
test("matches pattern starting with ./", () => {
const result = matchFilesAgainstRule(updatedFiles, ["./src/core/**"], root);
expect(result).toEqual([resolve("project/src/core/index.ts")]);
});
test("matches pattern ending with /**/", () => {
const result = matchFilesAgainstRule(updatedFiles, ["src/**/"], root);
expect(result).toContain(resolve("project/src/core/index.ts"));
expect(result).toContain(resolve("project/src/utils/helpers.ts"));
expect(result).toContain(resolve("project/src/models/searches/utils.ts"));
});
test("matches pattern with regex metacharacters in path", () => {
const specialFiles = [
resolve("project/src/utils/file[1].ts"),
resolve("project/src/utils/file(2).ts"),
];
const result = matchFilesAgainstRule(specialFiles, ["src/utils/**"], root);
expect(result).toEqual(specialFiles);
});
test("matches hidden file in subdirectory", () => {
const hiddenFile = resolve("project/src/.hidden/config.ts");
const result = matchFilesAgainstRule([hiddenFile], ["src/**"], root);
expect(result).toEqual([hiddenFile]);
});
test("matches file with unicode characters in path", () => {
const unicodeFile = resolve("project/src/设计/模块.ts");
const result = matchFilesAgainstRule([unicodeFile], ["src/**"], root);
expect(result).toEqual([unicodeFile]);
});
test("matches file with spaces and parentheses in path", () => {
const spacedFile = resolve("project/src/utils/my file (copy).ts");
const result = matchFilesAgainstRule([spacedFile], ["src/utils/**"], root);
expect(result).toEqual([spacedFile]);
});
});
10 changes: 8 additions & 2 deletions shared/file-matcher/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ const normalizePath = (filePath: string) => filePath.replace(/\\/g, "/");

function globToRegExp(glob: string | RegExp): RegExp {
if (glob instanceof RegExp) return glob;
const escaped = glob

let normalizedGlob = normalizePath(glob.trim().replace(/^\.?\/+/, ""));

// Auto-expand trailing slash to recursive match
if (normalizedGlob.endsWith("/")) normalizedGlob += "**";

const escaped = normalizedGlob
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
.replace(/\*\*/g, "§§")
.replace(/\*/g, "[^/]*")
Expand All @@ -22,7 +28,7 @@ export function matchFilesAgainstRule(
const regexes = rulePattern.map(globToRegExp);
const normalizedRootDir = normalizePath(rootDir);
return files.filter((file) => {
const normalizedFile = normalizePath(file);
const normalizedFile = normalizePath(file.trim());
const relativePath = normalizePath(
relative(normalizedRootDir, normalizedFile)
);
Expand Down
3 changes: 2 additions & 1 deletion shared/messages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ const sharedErrors = [
"𐄂 _committers_: That commit screams betrayal. _goodfellas_ heard it loud and clear.",
"𐄂 _committers_: You broke something built on trust. Now it needs fixing.",
"𐄂 _committers_, this act smells like ambition. Careful—ambition got Fredo killed.",
"𐄂 _committers_: You messed up. Big. _goodfellas_ are already talking.",
"𐄂 _committers_: You acted like a enemy. We don’t deal with enemies.",
"𐄂 _committers_: That move lacked honor. Fix it or fade out.",
"𐄂 _committers_: You weren’t subtle. You weren’t careful. Call _goodfella_ to clear things out.",
Expand All @@ -75,10 +74,12 @@ const messagesMap: Record<MessageType, string[]> = {
],
[MessageType.Error]: [
"𐄂 _committers_! You need permission from my trusted associate: _goodfellas_. Nobody touches this without approval.",
"𐄂 _committers_: You messed up. Big. _goodfellas_ is already talking.",
...sharedErrors,
],
[MessageType.MultiErrors]: [
"𐄂 _committers_! You need permission from my trusted associates: _goodfellas_. Nobody touches this without approval.",
"𐄂 _committers_: You messed up. Big. _goodfellas_ are already talking.",
...sharedErrors,
],
[MessageType.Warning]: [
Expand Down