diff --git a/README.md b/README.md
index 2f82ebe..f4e47e0 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-
+
[](https://github.com/DoneDeal0/codefather/actions/workflows/ci.yml)
@@ -72,7 +72,7 @@ npm install @donedeal0/codefather --save-dev
- 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.
+ - `overwrite`: overwrites 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
@@ -87,8 +87,8 @@ You can either add a script shortcut in your `package.json`:
Or directly run the commands with `npx`:
```bash
-npx codefather
npx codefather-init
+npx codefather
```
## CONFIG
@@ -188,6 +188,18 @@ git config user.username # return DonCorleone
In a Github Action, `codefather` will use Github's API, so you don't have to worry about the git config.
+## How to Write Rules
+
+- Match all files in a folder (recursively): `src/myfolder/`
+- Match a specific file: `src/myfolder/file.ts`
+- Match files by extension in a folder (glob): `src/folder/*.css`
+- Match files by extension in a folder (regex): `/^src\/folder\/.*\.css$/`
+- Match any file in any subfolder: `src/**`
+- Match dotfiles: `.env`
+- Use `*` for single-level matches, `**` for recursive matches
+
+ℹ️ *More examples are available in the test files. Codefather's matching patterns follow classic file matcher rules, like GitHub CODEOWNERS.*
+
# GITHUB ACTION
diff --git a/cli/index.ts b/cli/index.ts
index fcefb95..079b036 100755
--- a/cli/index.ts
+++ b/cli/index.ts
@@ -1,5 +1,4 @@
#!/usr/bin/env node
import { runCheck } from "./run-check/index.js";
-export * from "@shared/models";
runCheck();
diff --git a/package-lock.json b/package-lock.json
index c762e9f..cad91bb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,7 +11,7 @@
"dependencies": {
"@actions/github": "^6.0.1",
"@octokit/rest": "^22.0.0",
- "tsx": "^4.20.3"
+ "esbuild": "^0.25.8"
},
"bin": {
"codefather": "dist/index.js",
@@ -6971,6 +6971,7 @@
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
@@ -7046,7 +7047,10 @@
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz",
"integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==",
+ "dev": true,
"license": "MIT",
+ "optional": true,
+ "peer": true,
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
},
@@ -13463,7 +13467,10 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
"license": "MIT",
+ "optional": true,
+ "peer": true,
"funding": {
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
@@ -14909,7 +14916,10 @@
"version": "4.20.3",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.3.tgz",
"integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==",
+ "dev": true,
"license": "MIT",
+ "optional": true,
+ "peer": true,
"dependencies": {
"esbuild": "~0.25.0",
"get-tsconfig": "^4.7.5"
diff --git a/package.json b/package.json
index b395390..ebf30d4 100644
--- a/package.json
+++ b/package.json
@@ -1,24 +1,18 @@
{
"name": "@donedeal0/codefather",
- "version": "1.0.0",
+ "version": "1.0.7",
"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"
],
- "main": "dist/index.cjs",
- "module": "dist/index.js",
+ "type": "module",
"types": "dist/index.d.ts",
- "publishConfig": {
- "access": "public"
- },
- "exports": {
- ".": {
- "types": "./dist/index.d.ts",
- "import": "./dist/index.js",
- "require": "./dist/index.cjs"
- }
+ "bin": {
+ "codefather": "./dist/index.mjs",
+ "codefather-github": "./dist/scripts/github.mjs",
+ "codefather-init": "./dist/scripts/init.mjs"
},
"repository": {
"type": "git",
@@ -30,9 +24,11 @@
"funding": {
"type": "github",
"url": "https://github.com/sponsors/DoneDeal0"
+ },
+ "publishConfig": {
+ "access": "public"
},
"readme": "./README.md",
- "type": "module",
"declaration": true,
"keywords": [
"codeowners",
@@ -74,11 +70,6 @@
"godfather",
"authorization"
],
- "bin": {
- "codefather": "./dist/index.js",
- "codefather-github": "./dist/scripts/github.js",
- "codefather-init": "./dist/scripts/init.js"
- },
"scripts": {
"build": "tsup",
"codefather-github": "npm run build && node dist/scripts/github.js",
@@ -94,7 +85,7 @@
"dependencies": {
"@actions/github": "^6.0.1",
"@octokit/rest": "^22.0.0",
- "tsx": "^4.20.3"
+ "esbuild": "^0.25.8"
},
"devDependencies": {
"@commitlint/cli": "^19.8.1",
@@ -114,7 +105,7 @@
"swc-loader": "^0.2.6",
"ts-node": "^10.9.2",
"tsup": "^8.5.0",
- "typescript-eslint": "^8.38.0",
- "typescript": "^5.8.3"
+ "typescript": "^5.8.3",
+ "typescript-eslint": "^8.38.0"
}
}
diff --git a/shared/loader/import-data-buffer.ts b/shared/loader/import-data-buffer.ts
new file mode 100644
index 0000000..19494bc
--- /dev/null
+++ b/shared/loader/import-data-buffer.ts
@@ -0,0 +1,3 @@
+export async function importDataBuffer(dataUrl: string) {
+ return import(dataUrl);
+}
diff --git a/shared/loader/index.ts b/shared/loader/index.ts
index 6130ee8..7bcd087 100644
--- a/shared/loader/index.ts
+++ b/shared/loader/index.ts
@@ -1,9 +1,10 @@
+import { transform } from "esbuild";
import fs from "fs";
import path from "path";
-import { pathToFileURL } from "url";
import { getRandomMessage } from "@shared/messages";
import { MessageType, type CodefatherConfig } from "@shared/models";
import { safeJSONParse } from "@shared/parser";
+import { importDataBuffer } from "./import-data-buffer";
export async function loadConfig(): Promise {
try {
@@ -13,9 +14,13 @@ export async function loadConfig(): Promise {
const jsonPath = path.resolve(root, "codefather.json");
if (fs.existsSync(tsPath)) {
- const { register } = await import("tsx/esm/api");
- register();
- const config = await import(pathToFileURL(tsPath).href);
+ const tsCode = fs.readFileSync(tsPath, "utf-8");
+ const { code } = await transform(tsCode, {
+ loader: "ts",
+ format: "esm",
+ });
+ const dataUrl = `data:text/javascript;base64,${Buffer.from(code).toString("base64")}`;
+ const config = await importDataBuffer(dataUrl);
// a typescript file import may have several 'default' levels depending on the environment
return config?.default?.default || config?.default || config;
}
diff --git a/shared/loader/loader.test.ts b/shared/loader/loader.test.ts
index 22c0f66..ad0b756 100644
--- a/shared/loader/loader.test.ts
+++ b/shared/loader/loader.test.ts
@@ -1,11 +1,21 @@
-import { writeFileSync, unlinkSync } from "fs";
+import { writeFileSync, unlinkSync, existsSync } from "fs";
import { resolve } from "path";
import { loadConfig } from ".";
+jest.mock("./import-data-buffer", () => ({
+ importDataBuffer: jest.fn(async () => ({
+ default: { rules: [{ match: ["src/**"], goodfellas: ["sonny"] }] },
+ })),
+}));
+
const tsConfigPath = resolve(process.cwd(), "codefather.ts");
const jsonConfigPath = resolve(process.cwd(), "codefather.json");
describe("loadConfig", () => {
+ afterEach(() => {
+ if (existsSync(tsConfigPath)) unlinkSync(tsConfigPath);
+ if (existsSync(jsonConfigPath)) unlinkSync(jsonConfigPath);
+ });
test("returns config when codefather.ts exists", async () => {
writeFileSync(
tsConfigPath,
@@ -14,7 +24,6 @@ describe("loadConfig", () => {
};`
);
const result = await loadConfig();
- unlinkSync(tsConfigPath);
expect(result?.rules?.[0]?.goodfellas).toEqual(["sonny"]);
});
test("returns config when codefather.json exists", async () => {
@@ -25,7 +34,6 @@ describe("loadConfig", () => {
})
);
const result = await loadConfig();
- unlinkSync(jsonConfigPath);
expect(result?.rules?.[0]?.goodfellas).toEqual(["sonny"]);
});
test("throws an error if codefather.json is not properly formatted", async () => {
@@ -33,22 +41,13 @@ describe("loadConfig", () => {
jsonConfigPath,
`{ rules: { match: ["src/**"] goodfellas: ["sonny"] }] }`
);
- try {
- await loadConfig();
- } catch (err) {
- unlinkSync(jsonConfigPath);
- expect(err instanceof Error ? err.message : err).toBe(
- "Your JSON file is invalid. You gotta respect the rules if you want my help."
- );
- }
+ await expect(loadConfig()).rejects.toThrow(
+ "Your codefather.json file is invalid. You gotta respect the rules if you want my help."
+ );
});
test("throws an error when no codefather.(ts|json) exists", async () => {
- try {
- await loadConfig();
- } catch (err) {
- expect(err instanceof Error ? err.message : err).toBe(
- "𐄂 The codefather.ts file doesn't exist. Maybe someone whacked it?"
- );
- }
+ await expect(loadConfig()).rejects.toThrow(
+ "𐄂 The codefather.ts file doesn't exist. Maybe someone whacked it?"
+ );
});
});
diff --git a/shared/parser/index.ts b/shared/parser/index.ts
index a9de620..fb799e5 100644
--- a/shared/parser/index.ts
+++ b/shared/parser/index.ts
@@ -4,7 +4,7 @@ export function safeJSONParse(json: string): T {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (_) {
throw new Error(
- "Your JSON file is invalid. You gotta respect the rules if you want my help."
+ "Your codefather.json file is invalid. You gotta respect the rules if you want my help."
);
}
}
diff --git a/tsup.config.ts b/tsup.config.ts
index b471ab9..598ad4a 100644
--- a/tsup.config.ts
+++ b/tsup.config.ts
@@ -7,18 +7,19 @@ export default defineConfig([
"scripts/init": "scripts/init/index.ts",
"scripts/github": "scripts/github/index.ts",
},
- format: ["cjs", "esm"],
dts: {
- entry: ["cli/index.ts"],
+ entry: ["shared/models/index.ts"],
resolve: true,
},
- splitting: true,
+ format: ["esm"],
+ splitting: false,
clean: true,
treeshake: true,
shims: true,
minify: true,
platform: "node",
- name: "MAIN",
- external: ["tsx"],
+ name: "CLI",
+ external: ["esbuild"],
+ outExtension: () => ({ js: ".mjs" }),
},
]);