runHook static method
Future<HookRunResult>
runHook({
- required String hookName,
- required HooksConfigModel hooksConfig,
- String? configPath,
- bool exitOnFailure = true,
Executes all steps of the hook named hookName.
Called both manually via frx hooks run <name> and automatically
when git triggers the hook script.
Returns a HookRunResult and exits with code 1 if the hook fails and
stopOnFailure is true — this is what causes git to abort the commit.
Implementation
static Future<HookRunResult> runHook({
required String hookName,
required HooksConfigModel hooksConfig,
String? configPath,
bool exitOnFailure = true,
}) async {
final hook = hooksConfig.hooks[hookName];
if (hook == null) {
print('❌ Hook "$hookName" is not configured in config.yaml.');
print(' Available hooks: ${hooksConfig.hooks.keys.join(', ')}');
if (exitOnFailure) exit(1);
return HookRunResult(
hookName: hookName,
success: false,
totalDuration: Duration.zero,
);
}
if (!hook.enabled) {
print('ℹ️ Hook "$hookName" is disabled (enabled: false). Skipping.');
return HookRunResult(
hookName: hookName,
success: true,
totalDuration: Duration.zero,
);
}
final totalStopwatch = Stopwatch()..start();
print('');
_printHookBanner(hookName, hook);
final stepResults = <HookStepResult>[];
bool overallSuccess = true;
// ── Option A: delegate to a named FRX pipeline ───────────────────────────
if (hook.hasPipeline) {
print(' 🔗 Delegating to FRX pipeline: "${hook.runPipeline}"');
print('');
try {
await FlutterReleaseXHelpers.executePipeline(
pipelineName: hook.runPipeline);
} catch (e) {
print('❌ Pipeline "${hook.runPipeline}" failed: $e');
overallSuccess = false;
if (hook.stopOnFailure && exitOnFailure) {
_printSummary(hookName, stepResults, overallSuccess,
totalStopwatch.elapsed, hook.runPipeline);
exit(1);
}
}
totalStopwatch.stop();
_printSummary(hookName, stepResults, overallSuccess,
totalStopwatch.elapsed, hook.runPipeline);
return HookRunResult(
hookName: hookName,
success: overallSuccess,
stepResults: stepResults,
totalDuration: totalStopwatch.elapsed,
pipelineRan: hook.runPipeline,
);
}
// ── Option B: run inline steps ────────────────────────────────────────────
if (!hook.hasSteps) {
print(
'⚠️ Hook "$hookName" has no steps and no run_pipeline. Nothing to run.');
totalStopwatch.stop();
return HookRunResult(
hookName: hookName,
success: true,
totalDuration: totalStopwatch.elapsed,
);
}
for (int i = 0; i < hook.steps.length; i++) {
final step = hook.steps[i];
final stepStopwatch = Stopwatch()..start();
print(' [${i + 1}/${hook.steps.length}] ▶ ${step.name}');
if (step.description != null) {
print(' ${step.description}');
}
// Validate working directory
if (step.workingDirectory != null) {
if (!Directory(step.workingDirectory!).existsSync()) {
print(
' ❌ working_directory "${step.workingDirectory}" does not exist. Aborting step.');
stepStopwatch.stop();
final res = HookStepResult(
stepName: step.name,
passed: false,
duration: stepStopwatch.elapsed,
note: 'working_directory not found',
);
stepResults.add(res);
if (!step.allowFailure && hook.stopOnFailure) {
overallSuccess = false;
if (exitOnFailure) {
_printSummary(
hookName, stepResults, false, totalStopwatch.elapsed, null);
exit(1);
}
}
continue;
}
}
final result = await FlutterReleaseXHelpers.executeCommand(
step.command,
env: step.env,
workingDirectory: step.workingDirectory,
timeoutSeconds: step.timeout,
);
stepStopwatch.stop();
final elapsed = stepStopwatch.elapsed;
if (result.exitCode == 0) {
print(' ✅ ${step.name} — passed (${_formatDuration(elapsed)})');
stepResults.add(HookStepResult(
stepName: step.name,
passed: true,
duration: elapsed,
));
} else if (step.allowFailure) {
print(' ⚠️ ${step.name} — failed (allow_failure, continuing)');
stepResults.add(HookStepResult(
stepName: step.name,
passed: false,
warned: true,
duration: elapsed,
note: 'allowed failure',
));
} else {
print(
' ❌ ${step.name} — FAILED (exit code ${result.exitCode}) (${_formatDuration(elapsed)})');
overallSuccess = false;
stepResults.add(HookStepResult(
stepName: step.name,
passed: false,
duration: elapsed,
));
if (hook.stopOnFailure) {
_printSummary(
hookName, stepResults, false, totalStopwatch.elapsed, null);
if (exitOnFailure) exit(1);
break;
}
}
}
totalStopwatch.stop();
_printSummary(
hookName, stepResults, overallSuccess, totalStopwatch.elapsed, null);
if (!overallSuccess && exitOnFailure) exit(1);
return HookRunResult(
hookName: hookName,
success: overallSuccess,
stepResults: stepResults,
totalDuration: totalStopwatch.elapsed,
);
}