Overview
ANDO hooks are .csando scripts that run automatically before and after CLI commands. They execute on the host machine (not in Docker) and are useful for:
- Pre-build cleanup - Remove temporary files, conflict files, or stale artifacts
- Validation - Run tests or checks before allowing a command to proceed
- Notifications - Send Slack/Discord messages after releases
- Auto-updates - Update local tool installations after publishing
- Environment setup - Configure environment before builds
Hook Types
| Hook | When It Runs |
|---|---|
ando-pre.csando | Before every command |
ando-post.csando | After every command |
ando-pre-{cmd}.csando | Before a specific command |
ando-post-{cmd}.csando | After a specific command |
Execution order:
ando-pre.csando(general pre-hook)ando-pre-{cmd}.csando(command-specific pre-hook)- Command executes
ando-post-{cmd}.csando(command-specific post-hook)ando-post.csando(general post-hook)
File Locations
ANDO searches for hooks in these locations (first found wins):
./scripts/ando-{hook}.csando./ando-{hook}.csando
Recommended structure:
my-project/
├── build.csando
├── scripts/
│ ├── ando-pre.csando # Before every command
│ ├── ando-post.csando # After every command
│ ├── ando-pre-run.csando # Before 'ando run'
│ ├── ando-post-release.csando # After 'ando release'
│ └── ando-pre-bump.csando # Before 'ando bump'
└── src/
└── ...
Hook API
Hooks have access to a subset of the build script API:
| Global | Type | Description |
|---|---|---|
Root | BuildPath | Project root path. Supports / operator for paths. |
Log | HookLogOperations | Logging: Log.Info(), Log.Warning(), Log.Error(), Log.Debug() |
Shell | ShellOperations | Execute commands: await Shell.RunAsync("cmd", "args") |
Env(name) | Function | Get environment variable. Use Env("NAME", required: false) for optional. |
Directory(path) | Function | Create a DirectoryRef from a path. |
Environment Variables
These environment variables are available in hooks:
| Variable | Available In | Description |
|---|---|---|
ANDO_COMMAND | All hooks | The command being executed (run, bump, commit, etc.) |
ANDO_EXIT_CODE | Post-hooks only | Exit code of the command (0 = success) |
ANDO_OLD_VERSION | bump hooks | Version before the bump |
ANDO_NEW_VERSION | post-bump only | Version after the bump |
ANDO_BUMP_TYPE | bump hooks | Type of bump: patch, minor, or major |
Pre-Hook Behavior
Pre-hooks can abort the command by:
- Throwing an exception
- Returning a non-zero exit code from
Shell.RunAsync()
// scripts/ando-pre-bump.csando
// Abort bump if tests fail
var result = await Shell.RunAsync("dotnet", "test", "--no-build");
if (result.ExitCode != 0)
{
throw new Exception("Tests must pass before bumping version");
}
Post-Hook Behavior
Post-hooks cannot abort - they only warn on failure since the command has already completed.
Note: For release and ship commands, post-hooks only run when the command succeeds (exit code 0). This prevents post-release actions (like auto-updating tools or sending notifications) from running after a failed release.
// scripts/ando-post-release.csando
// Update tool after release (failures don't block)
try
{
await Shell.RunAsync("dotnet", "tool", "update", "-g", "mytool");
Log.Info("Tool updated successfully");
}
catch (Exception ex)
{
Log.Warning($"Auto-update failed: {ex.Message}");
// Hook continues - doesn't fail the release
}
Command Names
Use these command names in hook filenames:
| Command | Hook Names |
|---|---|
ando or ando run | ando-pre-run.csando, ando-post-run.csando |
ando commit | ando-pre-commit.csando, ando-post-commit.csando |
ando bump | ando-pre-bump.csando, ando-post-bump.csando |
ando docs | ando-pre-docs.csando, ando-post-docs.csando |
ando release | ando-pre-release.csando, ando-post-release.csando |
ando ship | ando-pre-ship.csando, ando-post-ship.csando |
ando clean | ando-pre-clean.csando, ando-post-clean.csando |
ando verify | ando-pre-verify.csando, ando-post-verify.csando |
Examples
Pre-Build Cleanup
Remove temporary files before every build:
// scripts/ando-pre.csando
var patterns = new[] { "*.sync-conflict-*", "*.orig", "*~", ".DS_Store" };
var removed = 0;
foreach (var pattern in patterns)
{
var files = System.IO.Directory
.EnumerateFiles(Root, pattern, System.IO.SearchOption.AllDirectories)
.Where(f => !f.Contains("node_modules") && !f.Contains(".git"));
foreach (var file in files)
{
try
{
System.IO.File.Delete(file);
removed++;
}
catch { }
}
}
if (removed > 0)
{
Log.Info($"Cleaned up {removed} temporary file(s)");
}
Validate Before Bump
Ensure tests pass before version bump:
// scripts/ando-pre-bump.csando
Log.Info("Running tests before version bump...");
var result = await Shell.RunAsync("dotnet", "test", "--no-build");
if (result.ExitCode != 0)
{
throw new Exception("All tests must pass before bumping version");
}
Log.Info("Tests passed!");
Auto-Update After Release
Update the local tool installation after publishing:
// scripts/ando-post-release.csando
// Skip if the release failed
var exitCode = Env("ANDO_EXIT_CODE", required: false);
if (exitCode != "0")
{
Log.Warning($"Release failed (exit code {exitCode}), skipping auto-update");
return;
}
var version = Env("ANDO_NEW_VERSION", required: false);
if (string.IsNullOrEmpty(version))
{
Log.Warning("ANDO_NEW_VERSION not set, skipping auto-update");
return;
}
Log.Info($"Updating global tool to v{version}...");
// Retry logic for NuGet CDN propagation
for (int i = 0; i < 10; i++)
{
var result = await Shell.RunAsync("dotnet", "tool", "update", "-g", "mytool");
if (result.ExitCode == 0)
{
Log.Info("Tool updated successfully!");
return;
}
Log.Info($" Waiting for NuGet... ({i + 1}/10)");
await System.Threading.Tasks.Task.Delay(15000);
}
Log.Warning("Auto-update failed. Run manually: dotnet tool update -g mytool");
Notify on Release
Send a Slack notification after release:
// scripts/ando-post-release.csando
var webhookUrl = Env("SLACK_WEBHOOK_URL", required: false);
if (string.IsNullOrEmpty(webhookUrl)) return;
var version = Env("ANDO_NEW_VERSION", required: false) ?? "unknown";
var payload = $@"{{""text"": "":rocket: Released v{version}!""}}";
using var client = new System.Net.Http.HttpClient();
var content = new System.Net.Http.StringContent(
payload, System.Text.Encoding.UTF8, "application/json");
await client.PostAsync(webhookUrl, content);
Log.Info("Sent Slack notification");
Conditional Hook
Only run for specific commands:
// scripts/ando-pre.csando
var command = Env("ANDO_COMMAND", required: false) ?? "run";
// Only clean before 'run', not 'verify' or 'clean'
if (command != "run") return;
// Cleanup logic here...
Tips
- Use
scripts/directory - Keeps hooks organized and separate from build scripts - Handle errors gracefully - Post-hooks should warn, not fail
- Check command type - Use
ANDO_COMMANDto run conditionally - Test hooks - Run
ando verifyto test pre-hooks without executing the build - Timeout - Hooks have a 5-minute timeout to prevent hangs
See Also
- Pre-Build Cleanup Hook - Full cleanup example
- Post-Release Auto-Update - NuGet update example
- CLI Reference - All ANDO commands