ANDO Hooks

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

HookWhen It Runs
ando-pre.csandoBefore every command
ando-post.csandoAfter every command
ando-pre-{cmd}.csandoBefore a specific command
ando-post-{cmd}.csandoAfter a specific command

Execution order:

  1. ando-pre.csando (general pre-hook)
  2. ando-pre-{cmd}.csando (command-specific pre-hook)
  3. Command executes
  4. ando-post-{cmd}.csando (command-specific post-hook)
  5. ando-post.csando (general post-hook)

File Locations

ANDO searches for hooks in these locations (first found wins):

  1. ./scripts/ando-{hook}.csando
  2. ./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:

GlobalTypeDescription
RootBuildPathProject root path. Supports / operator for paths.
LogHookLogOperationsLogging: Log.Info(), Log.Warning(), Log.Error(), Log.Debug()
ShellShellOperationsExecute commands: await Shell.RunAsync("cmd", "args")
Env(name)FunctionGet environment variable. Use Env("NAME", required: false) for optional.
Directory(path)FunctionCreate a DirectoryRef from a path.

Environment Variables

These environment variables are available in hooks:

VariableAvailable InDescription
ANDO_COMMANDAll hooksThe command being executed (run, bump, commit, etc.)
ANDO_EXIT_CODEPost-hooks onlyExit code of the command (0 = success)
ANDO_OLD_VERSIONbump hooksVersion before the bump
ANDO_NEW_VERSIONpost-bump onlyVersion after the bump
ANDO_BUMP_TYPEbump hooksType 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:

CommandHook Names
ando or ando runando-pre-run.csando, ando-post-run.csando
ando commitando-pre-commit.csando, ando-post-commit.csando
ando bumpando-pre-bump.csando, ando-post-bump.csando
ando docsando-pre-docs.csando, ando-post-docs.csando
ando releaseando-pre-release.csando, ando-post-release.csando
ando shipando-pre-ship.csando, ando-post-ship.csando
ando cleanando-pre-clean.csando, ando-post-clean.csando
ando verifyando-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_COMMAND to run conditionally
  • Test hooks - Run ando verify to test pre-hooks without executing the build
  • Timeout - Hooks have a 5-minute timeout to prevent hangs

See Also