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
120 changes: 47 additions & 73 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,38 @@

# WHAT IS IT?

Codefather protects your codebase by controlling who can change what. Set authorization levels, lock down files, and enforce your rules—offline via CLI or online with GitHub Actions.
**Codefather protects your codebase by controlling who can change what. Set authorization levels, lock down files, and enforce your rules—offline via CLI or online with GitHub Actions.**

ℹ️ The documentation is also available on our [website](https://donedeal0.gitbook.io/codefather/)!

<hr/>

## FEATURES
## CODEOWNERS COMPARISON

**Codefather** can serve as a drop-in replacement for GitHub’s CODEOWNERS—or play alongside it like a trusted consigliere.

GitHub’s [CODEOWNERS](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners) lets you define file owners in your codebase and automatically assign them as reviewers. No pull request can be merged until an appropriate codeowner has approved it.

**Codefather** offers more flexibility in assigning codeowners: support for various roles (teams, leads, developers), complex file-match rules, local execution, commit protection, and more. It can prevent unauthorized changes, warn developers, list prohibited files with error levels and contact points, block sensitive merges via GitHub Actions, and auto-assign reviewers when needed.

**Codefather** is designed to offer a delightful developer experience—a single config file for both CLI and GitHub Action usage, efficient commands to protect your codebase, automatic translation of CODEOWNERS into Codefather config, and over 100 personalized reactions to your commits.

**Whether you're enforcing strict governance or just want the Don watching over your commits, Codefather brings clarity, control, and charisma to your workflow.**

| FEATURE | CODEFATHER | GITHUB CODEOWNERS |
|--|--|--|
|Files and folders protection | ✅ | ✅ |
|Github Action | ✅ | ✅ |
|Auto-assign reviewers | ✅ | ✅ |
|Supports teams | ✅ | ✅ |
|Teams support | ✅ | ✅ |
|CLI + pre-commit | ✅ | ❌ |
|Roles hierarchy | ✅ | ❌ |
|Custom messages | ✅ | ❌ |
|Personalized feedbacks | ✅ | ❌ |
|Customizable config | ✅ | ❌ |
|Commit blockage | ✅ | ❌ |
|Godfather vibe | ✅ | ❌ |


## SCREENSHOTS

<div style="display: flex; flex-wrap: wrap; gap: 8px;">
Expand All @@ -52,23 +65,18 @@ npm install @donedeal0/codefather --save-dev

## USAGE

**Codefather** has 4 commands:
**Codefather** has 3 commands:

```bash
# checks if your access rules are respected in your repository
codefather

# creates a default config.ts at the root of your repository and add a `codefather` command to your package.json
codefather-init

# creates a default config.json at the root of your repository and add a `codefather` command to your package.json
codefather-init -- --json

# similar to the `codefather` command, but works in a Github Action environment
codefather-github
```
- `codefather`: checks if your access rules are respected in your repository.
- `codefather-init`: creates a default config at the root of your repository and adds a `codefather` command to your `package.json`.
- If a `.github/CODEOWNERS` file is present, it will be used to generate the config.
- Accepts two optional flags:
- `json`: generates a json config file instead of a `ts` one.
- `overwrite`: overwrite an existing codefather config.
- example: `npm run codefather-init json overwrite`
- `codefather-github`: similar to `codefather`, but designed to run in a GitHub Action environment

You can either add a script shortcut in your `package.json` (recommended):
You can either add a script shortcut in your `package.json`:

```json
"scripts": {
Expand All @@ -91,26 +99,17 @@ At the root of your repository, add a `codefather.ts` or `codefather.json` file.
import type { CodefatherConfig } from "@donedeal0/codefather";

export default {
caporegimes: [
{ name: "solozzo" },
{ name: "@lucabrasi", emailPrefix: "luca.brasi" },
],
caporegimes: [{ name: "solozzo" }, { name: "lucabrasi" }],
rules: [
{
match: ["package.json", "src/core/**", /^src\/app\/.*\.css$/],
goodfellas: [
{ name: "solozzo" },
{ name: "@tomhagen", emailPrefix: "tom.hagen" },
],
goodfellas: [{ name: "solozzo" }, { name: "tomhagen" }],
crews: ["clemenzaPeople"],
allowForgiveness: false,
},
{
match: ["src/models/**"],
goodfellas: [
{ name: "mike", emailPrefix: "michael.corleone" },
{ name: "sonny", emailPrefix: "sonny" },
],
goodfellas: [{ name: "mike" }, { name: "sonny" }],
allowForgiveness: true,
message: "Custom message to tell you to NOT TOUCH THE MODELS!",
},
Expand All @@ -124,7 +123,7 @@ export default {
autoAssignCaporegimes: true,
},
crews: {
clemenzaPeople: [{ name: "@paulieGatto" }, { name: "@lucabrasi" }],
clemenzaPeople: [{ name: "paulieGatto" }, { name: "lucabrasi" }],
},
} satisfies CodefatherConfig;
```
Expand All @@ -135,11 +134,8 @@ export default {

```ts
type CodefatherConfig {
/** List of users authorized to modify any files in your repository.
* name: github username.
* emailPrefix: prefix of the user email tied to their Github account (e.g. johnny.fontane@jazz.com should be johnny.fontane).
*/
caporegimes?: GitUser[];
/** List of users authorized to modify any files in your repository. */
caporegimes?: {name: string}[];
/** Rules that apply to protected files and folders */
rules: CodefatherRule[];
/** Options to refine the output */
Expand All @@ -153,11 +149,11 @@ type CodefatherConfig {
codeReviews?: {
/** If true, goodfellas responsible for modified files will be assigned on relevant pull requests, except the committers. Defaults to true. */
autoAssignGoodfellas: boolean;
/** If true, caporegimes will be assigned on every pull request except the committers. Defaults to false. */
/** If true, caporegimes will be assigned on every pull request, except the committers. Defaults to false. */
autoAssignCaporegimes: boolean;
};
/** Group users into teams. Crew names and composition are flexible in CLI mode but should match your github teams if used in a Github Action */
crews?: Record<CrewName, GitUser[]>;
crews?: Record<string, {name: string}[]>;
}
```

Expand All @@ -167,55 +163,31 @@ type CodefatherConfig {
type CodefatherRule {
/** List of the files or folders that can only be modified by a given list of users */
match: Array<RegExp | string>;
/** List of users authorized to modify the list of files or folders.
* name: github username.
* emailPrefix: prefix of the user email tied to their Github account (e.g. johnny.fontane@jazz.com should be johnny.fontane) .
*/
goodfellas: GitUser[];
/** List of authorized user crews. The crews must be defined at the root of your config when used in CLI mode. */
crews?: CrewName[];
/** List of users authorized to modify the list of files or folders. */
goodfellas: {name: string}[];
/** List of authorized user crews (teams). The crews must be defined at the root of your config when used in CLI mode. */
crews?: string[];
/** The message displayed if an unauthorized user tries to modify a protected file. If empty, a random message will be generated. */
message?: string;
/** If true, a warning will be issued and the script will not throw an error. False by default. */
allowForgiveness?: boolean;
}
```

> A `GitUser` is a developer in your codebase:

```ts
type GitUser = {
name?: string;
emailPrefix?: string;
};
```

You can use either the name, the email, or both, depending on your preference. The name should match your GitHub username (e.g. `@tom.hagen`). If you prefer the email, it should also be tied to your Github account.

For security reasons, only the email prefix is allowed in your config (e.g. `johnny.fontane@jazz.com` should be `johnny.fontane`).

In CLI mode, the name and email are retrieved from your Git config. You can set them like this:
The name should match your GitHub username (e.g. `tomhagen`). In CLI mode, the name is retrieved from your Git config. You can set it like this:

```bash
git config --global user.username "DonCorleone"
git config --global user.email "vito.corleone@nyc.com"
```

You can verify the current values like this:
You can verify the current value like this:

```bash
git config user.username # return DonCorleone
git config user.email # return vito.corleone@nyc.com
```

In a Github Action, `codefather` will use Github's API, so you don't have to worry about the git config.

> A `CrewName` is the name of a developers team

```ts
type CrewName = string;
```

<hr/>

# GITHUB ACTION
Expand Down Expand Up @@ -254,18 +226,20 @@ jobs:
To enforce reviews from codeowners (goodfellas, caporegimes and crews), consider enabling branch protection in your repository settings. To do it:

- Go to `settings`
- Click on `Branches`on the left sidebar
- Click on `Branches` on the left sidebar
- Select `Add classic branch protection rule`
- Check
- `Require a pull request before merging`
- `Require approvals`
- You're now under the protection of the Codefather.
- `Require review from Code Owners`
- `Require status checks to pass before merging`
- ✅ You're now under the protection of the Codefather.

<hr/>

# GLOSSARY

Codefather uses the Godfather's lingo. Although you don't need to know it to use the library, here are the definition of the special words used in the config file:
**Codefather** uses the Godfather's lingo. Although you don't need to know it to use the library, here are the definition of the special words used in the config file:

- `caporegime`: a captain who leads a group of mafia members. It's a tech-lead.
- `goodfella`: an appellation for a mobster (like "wise-guy" or "made man"). It's a developer.
Expand All @@ -280,7 +254,7 @@ This being said, if you don't like the gangster movie atmosphere and still want

## CREDITS

DoneDeal0 | talk.donedeal0[at]gmail.com
DoneDeal0 | talk.donedeal0@gmail.com

## SUPPORT

Expand Down
40 changes: 7 additions & 33 deletions cli/git-user/git-user.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,46 +17,20 @@ describe("getLocalGitUser", () => {
jest.clearAllMocks();
});

it("returns an user with both email and username", () => {
(execSync as jest.Mock)
.mockReturnValueOnce("tom.hagen@don.com")
.mockReturnValueOnce("@tomhagen");
expect(getLocalGitUser()).toEqual({
name: "@tomhagen",
emailPrefix: "tom.hagen",
});
});

it("returns an user with email only", () => {
(execSync as jest.Mock)
.mockReturnValueOnce("tom.hagen@don.com")
.mockReturnValueOnce(undefined);
expect(getLocalGitUser()).toEqual({
name: undefined,
emailPrefix: "tom.hagen",
});
});

it("returns an user with username only", () => {
(execSync as jest.Mock)
.mockReturnValueOnce(undefined)
.mockReturnValueOnce("@tomhagen");
expect(getLocalGitUser()).toEqual({
name: "@tomhagen",
emailPrefix: undefined,
});
it("returns an user with an username", () => {
(execSync as jest.Mock).mockReturnValueOnce("tomhagen");
expect(getLocalGitUser()).toEqual({ name: "tomhagen" });
});

it("throws an error if execSync fails", () => {
(execSync as jest.Mock).mockImplementationOnce(() => {
throw new Error("No email config");
throw new Error();
});
expect(() => getLocalGitUser()).toThrow("Error message");
});
it("throws an error if there is no email and no username in the git config", () => {
(execSync as jest.Mock)
.mockReturnValueOnce(undefined)
.mockReturnValueOnce(undefined);

it("throws an error if there is no username in the git config", () => {
(execSync as jest.Mock).mockReturnValueOnce(undefined);
expect(() => getLocalGitUser()).toThrow();
});
});
9 changes: 2 additions & 7 deletions cli/git-user/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
import { execSync } from "child_process";
import { getEmailPrefix } from "@shared/formatter";
import { getRandomMessage } from "@shared/messages";
import { GitUser, MessageType } from "@shared/models";

export function getLocalGitUser(): GitUser {
try {
const email = execSync("git config user.email", {
encoding: "utf8",
});
const name = execSync("git config user.username", {
encoding: "utf8",
});
if (!email && !name) {
if (!name) {
throw new Error();
}
const emailPrefix = getEmailPrefix(email);
return { name, emailPrefix };
return { name };
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (_: unknown) {
throw new Error(getRandomMessage(MessageType.NoGitConfig));
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
"description": "Codefather protects your codebase by controlling who can change what. Set authorization levels, lock down files, and enforce your rules—offline via CLI or online with GitHub Actions.",
"license": "ISC",
"author": "DoneDeal0",
"files": ["dist"],
"files": [
"dist"
],
"main": "dist/index.cjs",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"publishConfig": {
"access": "public"
"access": "public"
},
"exports": {
".": {
Expand Down
Loading