@fatsolutions/ganchos 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/configs/typescript/.prettierrc +8 -0
- package/configs/typescript/eslint.config.js +31 -0
- package/configs/typescript/install.js +16 -0
- package/configs/typescript/install.ts +18 -0
- package/dist/commands/disable.d.ts +3 -0
- package/dist/commands/disable.js +18 -0
- package/dist/commands/enable.d.ts +3 -0
- package/dist/commands/enable.js +25 -0
- package/dist/commands/generate.d.ts +3 -0
- package/dist/commands/generate.js +17 -0
- package/dist/commands/install.d.ts +3 -0
- package/dist/commands/install.js +30 -0
- package/dist/commands/list.d.ts +3 -0
- package/dist/commands/list.js +32 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -0
- package/dist/lib/config.d.ts +20 -0
- package/dist/lib/config.js +55 -0
- package/dist/lib/hooks.d.ts +11 -0
- package/dist/lib/hooks.js +87 -0
- package/dist/lib/paths.d.ts +27 -0
- package/dist/lib/paths.js +30 -0
- package/dist/types/index.d.ts +18 -0
- package/dist/types/index.js +13 -0
- package/hooks/pre-commit/javascript.sh +32 -0
- package/hooks/pre-commit/python.sh +107 -0
- package/hooks/pre-commit/typescript.sh +50 -0
- package/package.json +48 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import eslint from '@eslint/js';
|
|
2
|
+
import tseslint from "typescript-eslint";
|
|
3
|
+
import prettierConfig from 'eslint-config-prettier';
|
|
4
|
+
import { defineConfig } from "eslint/config";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export default defineConfig([
|
|
8
|
+
eslint.configs.recommended,
|
|
9
|
+
tseslint.configs.strict,
|
|
10
|
+
tseslint.configs.stylistic,
|
|
11
|
+
prettierConfig,
|
|
12
|
+
{
|
|
13
|
+
plugins: {
|
|
14
|
+
tseslint
|
|
15
|
+
},
|
|
16
|
+
rules: {
|
|
17
|
+
semi: "error",
|
|
18
|
+
"prefer-const": "error",
|
|
19
|
+
"@typescript-eslint/no-non-null-assertion": "warn",
|
|
20
|
+
"@typescript-eslint/no-unused-vars": [
|
|
21
|
+
"warn", // Or "error" or "off"
|
|
22
|
+
{
|
|
23
|
+
"argsIgnorePattern": "^_",
|
|
24
|
+
"varsIgnorePattern": "^_",
|
|
25
|
+
"caughtErrorsIgnorePattern": "^_",
|
|
26
|
+
"destructuredArrayIgnorePattern": "^_"
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
]);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const dependencies = {
|
|
2
|
+
"eslint": "9.39.2",
|
|
3
|
+
"@eslint/js": "9.39.2",
|
|
4
|
+
"eslint-config-prettier": "10.1.8", // includes prettier
|
|
5
|
+
"typescript-eslint": "8.51.0",
|
|
6
|
+
};
|
|
7
|
+
export const configFiles = [
|
|
8
|
+
"eslint.config.js",
|
|
9
|
+
".prettierrc"
|
|
10
|
+
];
|
|
11
|
+
export function getInstallCommand(deps) {
|
|
12
|
+
const packages = Object.entries(deps)
|
|
13
|
+
.map(([name, version]) => `${name}@${version}`)
|
|
14
|
+
.join(' ');
|
|
15
|
+
return `pnpm add -D ${packages}`;
|
|
16
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const dependencies: Record<string, string> = {
|
|
2
|
+
"eslint": "9.39.2",
|
|
3
|
+
"@eslint/js": "9.39.2",
|
|
4
|
+
"eslint-config-prettier": "10.1.8", // includes prettier
|
|
5
|
+
"typescript-eslint": "8.51.0",
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const configFiles: string[] = [
|
|
9
|
+
"eslint.config.js",
|
|
10
|
+
".prettierrc"
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
export function getInstallCommand(deps: Record<string, string>): string {
|
|
14
|
+
const packages = Object.entries(deps)
|
|
15
|
+
.map(([name, version]) => `${name}@${version}`)
|
|
16
|
+
.join(' ');
|
|
17
|
+
return `pnpm add -D ${packages}`;
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { disableLanguages } from "../lib/config.js";
|
|
2
|
+
async function disableCommand(languages) {
|
|
3
|
+
const projectRoot = process.cwd();
|
|
4
|
+
try {
|
|
5
|
+
await disableLanguages(projectRoot, languages);
|
|
6
|
+
console.log(`Disabled: ${languages.join(", ")}`);
|
|
7
|
+
console.log("Run `npx fat-hooks install` to apply changes.");
|
|
8
|
+
} catch (err) {
|
|
9
|
+
if (err instanceof Error) {
|
|
10
|
+
console.error(`Error: ${err.message}`);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
throw err;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
disableCommand
|
|
18
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { enableLanguages } from "../lib/config.js";
|
|
2
|
+
import { SUPPORTED_LANGUAGES } from "../types/index.js";
|
|
3
|
+
async function enableCommand(languages) {
|
|
4
|
+
const projectRoot = process.cwd();
|
|
5
|
+
for (const lang of languages) {
|
|
6
|
+
if (!SUPPORTED_LANGUAGES.includes(lang)) {
|
|
7
|
+
console.warn(`Warning: '${lang}' is not a supported language.`);
|
|
8
|
+
console.log(`Supported: ${SUPPORTED_LANGUAGES.join(", ")}`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
await enableLanguages(projectRoot, languages);
|
|
13
|
+
console.log(`Enabled: ${languages.join(", ")}`);
|
|
14
|
+
console.log("Run `npx fat-hooks install` to apply changes.");
|
|
15
|
+
} catch (err) {
|
|
16
|
+
if (err instanceof Error) {
|
|
17
|
+
console.error(`Error: ${err.message}`);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
throw err;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export {
|
|
24
|
+
enableCommand
|
|
25
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { generatePreCommitHook } from "../lib/hooks.js";
|
|
2
|
+
async function generateCommand() {
|
|
3
|
+
const projectRoot = process.cwd();
|
|
4
|
+
try {
|
|
5
|
+
const hookContent = await generatePreCommitHook(projectRoot);
|
|
6
|
+
process.stdout.write(hookContent);
|
|
7
|
+
} catch (err) {
|
|
8
|
+
if (err instanceof Error) {
|
|
9
|
+
console.error(`Error: ${err.message}`);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
throw err;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
generateCommand
|
|
17
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { installHook, installHookTools } from "../lib/hooks.js";
|
|
2
|
+
import { readConfig } from "../lib/config.js";
|
|
3
|
+
async function installCommand() {
|
|
4
|
+
const projectRoot = process.cwd();
|
|
5
|
+
console.log("Installing git hooks...");
|
|
6
|
+
try {
|
|
7
|
+
const config = await readConfig(projectRoot);
|
|
8
|
+
const enabledLanguages = config.languages.filter((l) => l.enabled).map((l) => l.name);
|
|
9
|
+
if (enabledLanguages.length === 0) {
|
|
10
|
+
console.log("No languages enabled. Enable languages first with:");
|
|
11
|
+
console.log(" npx fat-hooks enable javascript typescript python");
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
await installHookTools(projectRoot);
|
|
15
|
+
await installHook(projectRoot);
|
|
16
|
+
console.log(
|
|
17
|
+
`Generated pre-commit hook with languages: ${enabledLanguages.join(" ")}`
|
|
18
|
+
);
|
|
19
|
+
console.log("Git hooks installed successfully!");
|
|
20
|
+
} catch (err) {
|
|
21
|
+
if (err instanceof Error) {
|
|
22
|
+
console.error(`Error: ${err.message}`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
throw err;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
installCommand
|
|
30
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { readConfig } from "../lib/config.js";
|
|
2
|
+
import { SUPPORTED_LANGUAGES } from "../types/index.js";
|
|
3
|
+
async function listCommand() {
|
|
4
|
+
const projectRoot = process.cwd();
|
|
5
|
+
try {
|
|
6
|
+
const config = await readConfig(projectRoot);
|
|
7
|
+
console.log("Configured languages:");
|
|
8
|
+
for (const lang of config.languages) {
|
|
9
|
+
const status = lang.enabled ? "enabled" : "disabled";
|
|
10
|
+
console.log(` - ${lang.name} (${status})`);
|
|
11
|
+
}
|
|
12
|
+
const configuredNames = config.languages.map((l) => l.name);
|
|
13
|
+
const unconfigured = SUPPORTED_LANGUAGES.filter(
|
|
14
|
+
(l) => !configuredNames.includes(l)
|
|
15
|
+
);
|
|
16
|
+
if (unconfigured.length > 0) {
|
|
17
|
+
console.log("\nAvailable languages (not configured):");
|
|
18
|
+
for (const lang of unconfigured) {
|
|
19
|
+
console.log(` - ${lang}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
} catch (err) {
|
|
23
|
+
if (err instanceof Error) {
|
|
24
|
+
console.error(`Error: ${err.message}`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
throw err;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
listCommand
|
|
32
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { installCommand } from "./commands/install.js";
|
|
4
|
+
import { generateCommand } from "./commands/generate.js";
|
|
5
|
+
import { listCommand } from "./commands/list.js";
|
|
6
|
+
import { enableCommand } from "./commands/enable.js";
|
|
7
|
+
import { disableCommand } from "./commands/disable.js";
|
|
8
|
+
const program = new Command();
|
|
9
|
+
program.name("fat-hooks").description("Git Hook Manager for Polyglot Monorepos").version("1.0.0");
|
|
10
|
+
program.command("install").description("Install git hooks based on configuration").action(installCommand);
|
|
11
|
+
program.command("generate").description("Generate hook content to stdout (for inspection)").action(generateCommand);
|
|
12
|
+
program.command("list").description("List configured languages").action(listCommand);
|
|
13
|
+
program.command("enable").description("Enable one or more languages").argument("<languages...>", "Languages to enable").action(enableCommand);
|
|
14
|
+
program.command("disable").description("Disable one or more languages").argument("<languages...>", "Languages to disable").action(disableCommand);
|
|
15
|
+
program.parse();
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { FatHooksConfig } from '../types/index.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Read configuration from fat-hooks.json, creating default if missing.
|
|
5
|
+
*/
|
|
6
|
+
declare function readConfig(projectRoot: string): Promise<FatHooksConfig>;
|
|
7
|
+
/**
|
|
8
|
+
* Write configuration to fat-hooks.json.
|
|
9
|
+
*/
|
|
10
|
+
declare function writeConfig(projectRoot: string, config: FatHooksConfig): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Enable specified languages in the configuration.
|
|
13
|
+
*/
|
|
14
|
+
declare function enableLanguages(projectRoot: string, languages: string[]): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Disable specified languages in the configuration.
|
|
17
|
+
*/
|
|
18
|
+
declare function disableLanguages(projectRoot: string, languages: string[]): Promise<void>;
|
|
19
|
+
|
|
20
|
+
export { disableLanguages, enableLanguages, readConfig, writeConfig };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import { getConfigPath } from "./paths.js";
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_CONFIG,
|
|
5
|
+
SUPPORTED_LANGUAGES
|
|
6
|
+
} from "../types/index.js";
|
|
7
|
+
async function readConfig(projectRoot) {
|
|
8
|
+
const configPath = getConfigPath(projectRoot);
|
|
9
|
+
try {
|
|
10
|
+
const content = await fs.readFile(configPath, "utf-8");
|
|
11
|
+
return JSON.parse(content);
|
|
12
|
+
} catch (err) {
|
|
13
|
+
if (err.code === "ENOENT") {
|
|
14
|
+
await writeConfig(projectRoot, DEFAULT_CONFIG);
|
|
15
|
+
return DEFAULT_CONFIG;
|
|
16
|
+
}
|
|
17
|
+
throw err;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function writeConfig(projectRoot, config) {
|
|
21
|
+
const configPath = getConfigPath(projectRoot);
|
|
22
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
23
|
+
}
|
|
24
|
+
async function enableLanguages(projectRoot, languages) {
|
|
25
|
+
const config = await readConfig(projectRoot);
|
|
26
|
+
for (const lang of languages) {
|
|
27
|
+
if (!SUPPORTED_LANGUAGES.includes(lang)) {
|
|
28
|
+
console.warn(`Warning: Unknown language '${lang}', skipping`);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const langConfig = config.languages.find((l) => l.name === lang);
|
|
32
|
+
if (langConfig) {
|
|
33
|
+
langConfig.enabled = true;
|
|
34
|
+
} else {
|
|
35
|
+
config.languages.push({ name: lang, enabled: true });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
await writeConfig(projectRoot, config);
|
|
39
|
+
}
|
|
40
|
+
async function disableLanguages(projectRoot, languages) {
|
|
41
|
+
const config = await readConfig(projectRoot);
|
|
42
|
+
for (const lang of languages) {
|
|
43
|
+
const langConfig = config.languages.find((l) => l.name === lang);
|
|
44
|
+
if (langConfig) {
|
|
45
|
+
langConfig.enabled = false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
await writeConfig(projectRoot, config);
|
|
49
|
+
}
|
|
50
|
+
export {
|
|
51
|
+
disableLanguages,
|
|
52
|
+
enableLanguages,
|
|
53
|
+
readConfig,
|
|
54
|
+
writeConfig
|
|
55
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate the aggregated pre-commit hook content.
|
|
3
|
+
*/
|
|
4
|
+
declare function generatePreCommitHook(projectRoot: string): Promise<string>;
|
|
5
|
+
/**
|
|
6
|
+
* Install the generated hook to .git/hooks/pre-commit.
|
|
7
|
+
*/
|
|
8
|
+
declare function installHook(projectRoot: string): Promise<void>;
|
|
9
|
+
declare function installHookTools(projectRoot: string): Promise<void>;
|
|
10
|
+
|
|
11
|
+
export { generatePreCommitHook, installHook, installHookTools };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { execSync } from "child_process";
|
|
4
|
+
import { getHookScript, getGitHooksDir, getLanguageConfigDir } from "./paths.js";
|
|
5
|
+
import { readConfig } from "./config.js";
|
|
6
|
+
const HOOK_HEADER = `#!/bin/sh
|
|
7
|
+
# Auto-generated pre-commit hook
|
|
8
|
+
# DO NOT EDIT - managed by fat-hooks
|
|
9
|
+
|
|
10
|
+
set -e
|
|
11
|
+
|
|
12
|
+
echo "Running pre-commit checks..."
|
|
13
|
+
|
|
14
|
+
`;
|
|
15
|
+
const HOOK_FOOTER = `
|
|
16
|
+
echo "Pre-commit checks completed successfully!"
|
|
17
|
+
`;
|
|
18
|
+
async function generatePreCommitHook(projectRoot) {
|
|
19
|
+
const config = await readConfig(projectRoot);
|
|
20
|
+
const enabledLanguages = config.languages.filter((l) => l.enabled);
|
|
21
|
+
let hookContent = HOOK_HEADER;
|
|
22
|
+
for (const lang of enabledLanguages) {
|
|
23
|
+
const scriptPath = getHookScript("pre-commit", lang.name);
|
|
24
|
+
try {
|
|
25
|
+
const script = await fs.readFile(scriptPath, "utf-8");
|
|
26
|
+
hookContent += `# Language: ${lang.name}
|
|
27
|
+
`;
|
|
28
|
+
hookContent += script;
|
|
29
|
+
hookContent += "\n";
|
|
30
|
+
} catch {
|
|
31
|
+
console.warn(`Warning: Hook not found for language: ${lang.name}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
hookContent += HOOK_FOOTER;
|
|
35
|
+
return hookContent;
|
|
36
|
+
}
|
|
37
|
+
async function installHook(projectRoot) {
|
|
38
|
+
const gitDir = path.join(projectRoot, ".git");
|
|
39
|
+
try {
|
|
40
|
+
await fs.access(gitDir);
|
|
41
|
+
} catch {
|
|
42
|
+
throw new Error("Not in a git repository");
|
|
43
|
+
}
|
|
44
|
+
const hooksDir = getGitHooksDir(projectRoot);
|
|
45
|
+
await fs.mkdir(hooksDir, { recursive: true });
|
|
46
|
+
const hookContent = await generatePreCommitHook(projectRoot);
|
|
47
|
+
const hookPath = path.join(hooksDir, "pre-commit");
|
|
48
|
+
await fs.writeFile(hookPath, hookContent, { mode: 493 });
|
|
49
|
+
}
|
|
50
|
+
async function installHookTools(projectRoot) {
|
|
51
|
+
const config = await readConfig(projectRoot);
|
|
52
|
+
const enabledLanguages = config.languages.filter((l) => l.enabled);
|
|
53
|
+
for (const lang of enabledLanguages) {
|
|
54
|
+
const langConfigDir = getLanguageConfigDir(lang.name);
|
|
55
|
+
const installModulePath = path.join(langConfigDir, "install.js");
|
|
56
|
+
try {
|
|
57
|
+
await fs.access(installModulePath);
|
|
58
|
+
} catch {
|
|
59
|
+
console.log(`No install module for ${lang.name}, skipping tools`);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const installModule = await import(installModulePath);
|
|
63
|
+
if (installModule.dependencies && installModule.getInstallCommand) {
|
|
64
|
+
const cmd = installModule.getInstallCommand(installModule.dependencies);
|
|
65
|
+
console.log(`Installing ${lang.name} tools: ${cmd}`);
|
|
66
|
+
execSync(cmd, { cwd: projectRoot, stdio: "inherit" });
|
|
67
|
+
}
|
|
68
|
+
if (installModule.configFiles) {
|
|
69
|
+
for (const configFile of installModule.configFiles) {
|
|
70
|
+
const src = path.join(langConfigDir, configFile);
|
|
71
|
+
const dest = path.join(projectRoot, configFile);
|
|
72
|
+
try {
|
|
73
|
+
await fs.access(dest);
|
|
74
|
+
console.log(`Config ${configFile} already exists, skipping`);
|
|
75
|
+
} catch {
|
|
76
|
+
await fs.copyFile(src, dest);
|
|
77
|
+
console.log(`Copied ${configFile}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export {
|
|
84
|
+
generatePreCommitHook,
|
|
85
|
+
installHook,
|
|
86
|
+
installHookTools
|
|
87
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get the path to bundled hooks directory.
|
|
3
|
+
* Works whether installed globally, locally, or via npx.
|
|
4
|
+
*/
|
|
5
|
+
declare function getHooksDir(): string;
|
|
6
|
+
/**
|
|
7
|
+
* Get path to a specific hook script.
|
|
8
|
+
*/
|
|
9
|
+
declare function getHookScript(hookType: string, language: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Get the user's config file path (in their project root).
|
|
12
|
+
*/
|
|
13
|
+
declare function getConfigPath(projectRoot: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Get the .git/hooks directory for the current project.
|
|
16
|
+
*/
|
|
17
|
+
declare function getGitHooksDir(projectRoot: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Get the path to bundled configs directory.
|
|
20
|
+
*/
|
|
21
|
+
declare function getConfigsDir(): string;
|
|
22
|
+
/**
|
|
23
|
+
* Get path to a specific language's config directory.
|
|
24
|
+
*/
|
|
25
|
+
declare function getLanguageConfigDir(language: string): string;
|
|
26
|
+
|
|
27
|
+
export { getConfigPath, getConfigsDir, getGitHooksDir, getHookScript, getHooksDir, getLanguageConfigDir };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = path.dirname(__filename);
|
|
5
|
+
function getHooksDir() {
|
|
6
|
+
return path.resolve(__dirname, "../../hooks");
|
|
7
|
+
}
|
|
8
|
+
function getHookScript(hookType, language) {
|
|
9
|
+
return path.join(getHooksDir(), hookType, `${language}.sh`);
|
|
10
|
+
}
|
|
11
|
+
function getConfigPath(projectRoot) {
|
|
12
|
+
return path.join(projectRoot, ".ganchos.json");
|
|
13
|
+
}
|
|
14
|
+
function getGitHooksDir(projectRoot) {
|
|
15
|
+
return path.join(projectRoot, ".git", "hooks");
|
|
16
|
+
}
|
|
17
|
+
function getConfigsDir() {
|
|
18
|
+
return path.resolve(__dirname, "../../configs");
|
|
19
|
+
}
|
|
20
|
+
function getLanguageConfigDir(language) {
|
|
21
|
+
return path.join(getConfigsDir(), language);
|
|
22
|
+
}
|
|
23
|
+
export {
|
|
24
|
+
getConfigPath,
|
|
25
|
+
getConfigsDir,
|
|
26
|
+
getGitHooksDir,
|
|
27
|
+
getHookScript,
|
|
28
|
+
getHooksDir,
|
|
29
|
+
getLanguageConfigDir
|
|
30
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface LanguageConfig {
|
|
2
|
+
name: string;
|
|
3
|
+
enabled: boolean;
|
|
4
|
+
}
|
|
5
|
+
interface LanguageInstallModule {
|
|
6
|
+
dependencies: Record<string, string>;
|
|
7
|
+
configFiles: string[];
|
|
8
|
+
getInstallCommand: (deps: Record<string, string>) => string;
|
|
9
|
+
}
|
|
10
|
+
interface FatHooksConfig {
|
|
11
|
+
version: string;
|
|
12
|
+
languages: LanguageConfig[];
|
|
13
|
+
}
|
|
14
|
+
declare const SUPPORTED_LANGUAGES: readonly ["javascript", "typescript", "python"];
|
|
15
|
+
type SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number];
|
|
16
|
+
declare const DEFAULT_CONFIG: FatHooksConfig;
|
|
17
|
+
|
|
18
|
+
export { DEFAULT_CONFIG, type FatHooksConfig, type LanguageConfig, type LanguageInstallModule, SUPPORTED_LANGUAGES, type SupportedLanguage };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const SUPPORTED_LANGUAGES = ["javascript", "typescript", "python"];
|
|
2
|
+
const DEFAULT_CONFIG = {
|
|
3
|
+
version: "1.0.0",
|
|
4
|
+
languages: [
|
|
5
|
+
{ name: "javascript", enabled: false },
|
|
6
|
+
{ name: "typescript", enabled: false },
|
|
7
|
+
{ name: "python", enabled: false }
|
|
8
|
+
]
|
|
9
|
+
};
|
|
10
|
+
export {
|
|
11
|
+
DEFAULT_CONFIG,
|
|
12
|
+
SUPPORTED_LANGUAGES
|
|
13
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# JavaScript/Node.js pre-commit hook
|
|
2
|
+
echo "Running JavaScript checks..."
|
|
3
|
+
|
|
4
|
+
# Check if there are any JS/TS files to check
|
|
5
|
+
JS_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|jsx|mjs|cjs)$' || true)
|
|
6
|
+
if [ -n "$JS_FILES" ]; then
|
|
7
|
+
echo "Checking JavaScript files: $JS_FILES"
|
|
8
|
+
|
|
9
|
+
# Get unique package directories from modified files
|
|
10
|
+
PACKAGES=$(echo "$JS_FILES" | grep '^packages/' | cut -d'/' -f1-2 | sort -u || true)
|
|
11
|
+
|
|
12
|
+
# Check if we need to run tools from specific packages
|
|
13
|
+
HAS_ROOT_JS=$(echo "$JS_FILES" | grep -v '^packages/' || true)
|
|
14
|
+
|
|
15
|
+
# Run ESLint on all JavaScript files (from root with proper config detection)
|
|
16
|
+
if command -v npx >/dev/null 2>&1; then
|
|
17
|
+
echo " Running ESLint on JavaScript files..."
|
|
18
|
+
echo "$JS_FILES" | xargs npx eslint --fix || {
|
|
19
|
+
echo "ESLint failed. Please fix the issues before committing."
|
|
20
|
+
exit 1
|
|
21
|
+
}
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# Run Prettier on all JavaScript files (from root)
|
|
25
|
+
if command -v npx >/dev/null 2>&1; then
|
|
26
|
+
echo " Running Prettier on JavaScript files..."
|
|
27
|
+
echo "$JS_FILES" | xargs npx prettier --write || {
|
|
28
|
+
echo "Prettier failed. Please fix the issues before committing."
|
|
29
|
+
exit 1
|
|
30
|
+
}
|
|
31
|
+
fi
|
|
32
|
+
fi
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Python pre-commit hook
|
|
2
|
+
echo "Running Python checks..."
|
|
3
|
+
|
|
4
|
+
# Check if there are any Python files to check
|
|
5
|
+
PY_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.py$' || true)
|
|
6
|
+
if [ -n "$PY_FILES" ]; then
|
|
7
|
+
echo "Checking Python files: $PY_FILES"
|
|
8
|
+
|
|
9
|
+
# Get unique package directories from modified files
|
|
10
|
+
PACKAGES=$(echo "$PY_FILES" | grep '^packages/' | cut -d'/' -f1-2 | sort -u || true)
|
|
11
|
+
|
|
12
|
+
# Check if we have root-level Python files
|
|
13
|
+
ROOT_PY_FILES=$(echo "$PY_FILES" | grep -v '^packages/' || true)
|
|
14
|
+
|
|
15
|
+
# Run tools per package directory (if they have specific configs)
|
|
16
|
+
for package_dir in $PACKAGES; do
|
|
17
|
+
PACKAGE_PY_FILES=$(echo "$PY_FILES" | grep "^$package_dir/" || true)
|
|
18
|
+
if [ -n "$PACKAGE_PY_FILES" ]; then
|
|
19
|
+
echo " Checking Python files in $package_dir..."
|
|
20
|
+
|
|
21
|
+
# Run tools from package directory if they have local configs
|
|
22
|
+
if [ -f "$package_dir/pyproject.toml" ] || [ -f "$package_dir/setup.cfg" ]; then
|
|
23
|
+
(
|
|
24
|
+
cd "$package_dir"
|
|
25
|
+
RELATIVE_FILES=$(echo "$PACKAGE_PY_FILES" | sed "s|^$package_dir/||")
|
|
26
|
+
|
|
27
|
+
# Run Black formatter if available
|
|
28
|
+
if command -v black >/dev/null 2>&1; then
|
|
29
|
+
echo " Running Black formatter in $package_dir..."
|
|
30
|
+
echo "$RELATIVE_FILES" | xargs black --check || {
|
|
31
|
+
echo "Black formatting failed in $package_dir. Run 'black .' to fix formatting."
|
|
32
|
+
exit 1
|
|
33
|
+
}
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# Run isort if available
|
|
37
|
+
if command -v isort >/dev/null 2>&1; then
|
|
38
|
+
echo " Running isort in $package_dir..."
|
|
39
|
+
echo "$RELATIVE_FILES" | xargs isort --check-only || {
|
|
40
|
+
echo "isort failed in $package_dir. Run 'isort .' to fix import sorting."
|
|
41
|
+
exit 1
|
|
42
|
+
}
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# Run flake8 if available
|
|
46
|
+
if command -v flake8 >/dev/null 2>&1; then
|
|
47
|
+
echo " Running flake8 in $package_dir..."
|
|
48
|
+
echo "$RELATIVE_FILES" | xargs flake8 || {
|
|
49
|
+
echo "flake8 failed in $package_dir. Please fix the issues before committing."
|
|
50
|
+
exit 1
|
|
51
|
+
}
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
# Run mypy if available
|
|
55
|
+
if command -v mypy >/dev/null 2>&1; then
|
|
56
|
+
echo " Running mypy in $package_dir..."
|
|
57
|
+
echo "$RELATIVE_FILES" | xargs mypy || {
|
|
58
|
+
echo "mypy failed in $package_dir. Please fix the type issues before committing."
|
|
59
|
+
exit 1
|
|
60
|
+
}
|
|
61
|
+
fi
|
|
62
|
+
) || exit 1
|
|
63
|
+
fi
|
|
64
|
+
fi
|
|
65
|
+
done
|
|
66
|
+
|
|
67
|
+
# Run tools from root for all Python files (fallback and root files)
|
|
68
|
+
if [ -n "$ROOT_PY_FILES" ] || [ -z "$PACKAGES" ]; then
|
|
69
|
+
ALL_FILES="$PY_FILES"
|
|
70
|
+
|
|
71
|
+
# Run Black formatter if available
|
|
72
|
+
if command -v black >/dev/null 2>&1; then
|
|
73
|
+
echo " Running Black formatter from root..."
|
|
74
|
+
echo "$ALL_FILES" | xargs black --check || {
|
|
75
|
+
echo "Black formatting failed. Run 'black .' to fix formatting."
|
|
76
|
+
exit 1
|
|
77
|
+
}
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
# Run isort if available
|
|
81
|
+
if command -v isort >/dev/null 2>&1; then
|
|
82
|
+
echo " Running isort from root..."
|
|
83
|
+
echo "$ALL_FILES" | xargs isort --check-only || {
|
|
84
|
+
echo "isort failed. Run 'isort .' to fix import sorting."
|
|
85
|
+
exit 1
|
|
86
|
+
}
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
# Run flake8 if available
|
|
90
|
+
if command -v flake8 >/dev/null 2>&1; then
|
|
91
|
+
echo " Running flake8 from root..."
|
|
92
|
+
echo "$ALL_FILES" | xargs flake8 || {
|
|
93
|
+
echo "flake8 failed. Please fix the issues before committing."
|
|
94
|
+
exit 1
|
|
95
|
+
}
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
# Run mypy if available
|
|
99
|
+
if command -v mypy >/dev/null 2>&1; then
|
|
100
|
+
echo " Running mypy from root..."
|
|
101
|
+
echo "$ALL_FILES" | xargs mypy || {
|
|
102
|
+
echo "mypy failed. Please fix the type issues before committing."
|
|
103
|
+
exit 1
|
|
104
|
+
}
|
|
105
|
+
fi
|
|
106
|
+
fi
|
|
107
|
+
fi
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# TypeScript pre-commit hook
|
|
2
|
+
echo "Running TypeScript checks..."
|
|
3
|
+
|
|
4
|
+
# Check if there are any TS files to check
|
|
5
|
+
TS_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(ts|tsx)$' || true)
|
|
6
|
+
if [ -n "$TS_FILES" ]; then
|
|
7
|
+
echo "Checking TypeScript files: $TS_FILES"
|
|
8
|
+
|
|
9
|
+
# Get unique package directories from modified files
|
|
10
|
+
PACKAGES=$(echo "$TS_FILES" | grep '^packages/' | cut -d'/' -f1-2 | sort -u || true)
|
|
11
|
+
|
|
12
|
+
# Check each package that has TypeScript changes
|
|
13
|
+
for package_dir in $PACKAGES; do
|
|
14
|
+
if [ -f "$package_dir/tsconfig.json" ]; then
|
|
15
|
+
echo " Running TypeScript compiler check in $package_dir..."
|
|
16
|
+
(cd "$package_dir" && npx tsc --noEmit -p ./tsconfig.json) || {
|
|
17
|
+
echo "TypeScript compilation failed in $package_dir. Please fix the issues before committing."
|
|
18
|
+
exit 1
|
|
19
|
+
}
|
|
20
|
+
fi
|
|
21
|
+
done
|
|
22
|
+
|
|
23
|
+
# Run root-level TypeScript check if there are TS files in root or no packages detected
|
|
24
|
+
ROOT_TS_FILES=$(echo "$TS_FILES" | grep -v '^packages/' || true)
|
|
25
|
+
if [ -n "$ROOT_TS_FILES" ] && [ -f "tsconfig.json" ]; then
|
|
26
|
+
echo " Running TypeScript compiler check in root..."
|
|
27
|
+
npx tsc --noEmit -p ./tsconfig.json || {
|
|
28
|
+
echo "TypeScript compilation failed in root. Please fix the issues before committing."
|
|
29
|
+
exit 1
|
|
30
|
+
}
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# Run ESLint on all TypeScript files (from root with proper config detection)
|
|
34
|
+
if command -v npx >/dev/null 2>&1; then
|
|
35
|
+
echo " Running ESLint on TypeScript files..."
|
|
36
|
+
echo "$TS_FILES" | xargs npx eslint --fix || {
|
|
37
|
+
echo "ESLint failed. Please fix the issues before committing."
|
|
38
|
+
exit 1
|
|
39
|
+
}
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
# Run Prettier on all TypeScript files (from root)
|
|
43
|
+
if command -v npx >/dev/null 2>&1; then
|
|
44
|
+
echo " Running Prettier on TypeScript files..."
|
|
45
|
+
echo "$TS_FILES" | xargs npx prettier --write || {
|
|
46
|
+
echo "Prettier failed. Please fix the issues before committing."
|
|
47
|
+
exit 1
|
|
48
|
+
}
|
|
49
|
+
fi
|
|
50
|
+
fi
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fatsolutions/ganchos",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Git Hook Manager for Polyglot Monorepos, developed in house by FatSolutions",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"ganchos": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"hooks",
|
|
14
|
+
"configs"
|
|
15
|
+
],
|
|
16
|
+
"keywords": [
|
|
17
|
+
"git",
|
|
18
|
+
"hooks",
|
|
19
|
+
"pre-commit",
|
|
20
|
+
"monorepo",
|
|
21
|
+
"linting"
|
|
22
|
+
],
|
|
23
|
+
"author": "FatSolutions",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18.0.0"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"commander": "12.1.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "20.19.29",
|
|
33
|
+
"tsup": "^8.5.1",
|
|
34
|
+
"typescript": "5.9.3"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public",
|
|
38
|
+
"registry": "https://registry.npmjs.org"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsup && tsc -p tsconfig.configs.json",
|
|
42
|
+
"build:dev": "tsc -p tsconfig.build.json && tsc -p tsconfig.configs.json",
|
|
43
|
+
"dev": "tsup --watch",
|
|
44
|
+
"clean": "rm -rf dist configs/**/install.js",
|
|
45
|
+
"typecheck": "tsc --noEmit",
|
|
46
|
+
"bundle": "pnpm run clean && pnpm run build"
|
|
47
|
+
}
|
|
48
|
+
}
|