@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.
@@ -0,0 +1,8 @@
1
+ {
2
+ "tabWidth": 4,
3
+ "endOfLine": "lf",
4
+ "singleQuote": false,
5
+ "printWidth": 120,
6
+ "quoteProps": "preserve",
7
+ "trailingComma": "es5"
8
+ }
@@ -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,3 @@
1
+ declare function disableCommand(languages: string[]): Promise<void>;
2
+
3
+ export { disableCommand };
@@ -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,3 @@
1
+ declare function enableCommand(languages: string[]): Promise<void>;
2
+
3
+ export { enableCommand };
@@ -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,3 @@
1
+ declare function generateCommand(): Promise<void>;
2
+
3
+ export { generateCommand };
@@ -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,3 @@
1
+ declare function installCommand(): Promise<void>;
2
+
3
+ export { installCommand };
@@ -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,3 @@
1
+ declare function listCommand(): Promise<void>;
2
+
3
+ export { listCommand };
@@ -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
+ };
@@ -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
+ }