install static method

Future<void> install({
  1. required HooksConfigModel hooksConfig,
  2. String? configPath,
  3. String? projectDir,
})

Installs all enabled hooks from hooksConfig into .git/hooks/.

  • Creates .git/hooks/ if it doesn't exist.
  • Writes a shell script (macOS/Linux) or batch/ps1 shim (Windows).
  • Makes the script executable on Unix systems.
  • Idempotent: re-running replaces existing FRX-managed scripts.
  • Skips any non-FRX hook file already present (won't clobber custom hooks).

Implementation

static Future<void> install({
  required HooksConfigModel hooksConfig,
  String? configPath,
  String? projectDir,
}) async {
  // Locate .git directory
  final gitDir = await _findGitDir(projectDir ?? Directory.current.path);
  if (gitDir == null) {
    print(
        '❌ No .git directory found. Are you running this inside a git repository?');
    exit(1);
  }

  final hooksDir = Directory('${gitDir.path}${Platform.pathSeparator}hooks');
  if (!hooksDir.existsSync()) {
    hooksDir.createSync(recursive: true);
    print('📁 Created .git/hooks/ directory');
  }

  final enabledHooks = hooksConfig.enabledHooks;
  if (enabledHooks.isEmpty) {
    print('');
    print('â„šī¸  No hooks have enabled: true in your config.');
    print(
        '   Set enabled: true under a hook in config.yaml and re-run "frx hooks install".');
    return;
  }

  int installed = 0;
  int skipped = 0;

  print('');
  print('🔧 Installing ${enabledHooks.length} hook(s)...');
  print('');

  for (final entry in enabledHooks.entries) {
    final hookName = entry.key;
    final hookFile =
        File('${hooksDir.path}${Platform.pathSeparator}$hookName');

    // Check if an existing, non-FRX hook script is already there
    if (hookFile.existsSync()) {
      final existing = hookFile.readAsStringSync();
      if (!existing.contains(_kFrxHookMarker)) {
        print(
            '   âš ī¸  Skipping "$hookName" — a custom hook already exists and was NOT written by FRX.');
        print(
            '      To replace it, manually remove .git/hooks/$hookName then re-run.');
        skipped++;
        continue;
      }
    }

    final script = Platform.isWindows
        ? _buildWindowsScript(hookName, configPath)
        : _buildUnixScript(hookName, configPath);

    hookFile.writeAsStringSync(script);

    // Make executable on Unix
    if (!Platform.isWindows) {
      await Process.run('chmod', ['+x', hookFile.path]);
    }

    print('   ✅ Installed: .git/hooks/$hookName');
    installed++;
  }

  print('');
  if (installed > 0) {
    print(
        '🎉 Done! $installed hook(s) installed${skipped > 0 ? ', $skipped skipped' : ''}.');
    print('   Git will now run "frx hooks run <hook-name>" automatically.');
  } else if (skipped > 0) {
    print('âš ī¸  All hooks were skipped (custom hooks already present).');
  }
  print('');
}