Skip to content

Commit 162abc7

Browse files
committed
Implement pre-run hooks with .hooks.d support
Add pre-run hooks feature that executes scripts before main script execution. Hooks enable environment validation, dependency checking, authentication, and setup tasks. Implementation: - Hook discovery from .hooks.d directory with lexicographic ordering - Two hook types: executable (separate process) and sourced (same shell context) - Sourced hooks use .source suffix and can modify environment variables - Wrapper script generation for proper execution flow - --skip-hooks flag to bypass hook execution - Full environment variable injection (TOME_ROOT, TOME_SCRIPT_PATH, etc.) Tests: - 38 total tests covering unit, integration, and E2E scenarios - Comprehensive validation of hook discovery, execution, and environment handling - Deno E2E tests verify real-world usage with both tome-cli and wrapper Documentation: - Complete user guide in docs/hooks.md with examples and use cases - Updated README.md with hooks feature section - Example hooks in examples/.hooks.d for reference All tests passing. Feature ready for use.
1 parent b5d315d commit 162abc7

File tree

11 files changed

+2358
-3
lines changed

11 files changed

+2358
-3
lines changed

README.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,26 @@ case $1 in
176176
esac
177177
```
178178

179+
### Pre-Run Hooks
180+
181+
Execute custom scripts before your main scripts run. Perfect for:
182+
- Environment validation and setup
183+
- Dependency checking
184+
- Authentication checks
185+
- Audit logging
186+
187+
Create a `.hooks.d/` directory in your scripts root and add numbered hooks:
188+
189+
```bash
190+
# Executable hook - runs as separate process
191+
.hooks.d/00-check-deps
192+
193+
# Sourced hook - runs in same shell, can modify environment
194+
.hooks.d/05-set-env.source
195+
```
196+
197+
See [docs/hooks.md](./docs/hooks.md) for complete guide with examples.
198+
179199
### Flexible Root Detection
180200

181201
tome-cli determines the scripts root directory from multiple sources (in order of precedence):
@@ -200,10 +220,10 @@ This flexibility allows team members to customize locations without changing the
200220
- ✅ Environment variable injection
201221
- ✅ Structured logging with levels
202222
- ✅ Generated documentation
223+
- ✅ Pre-run hooks (.hooks.d folder execution)
203224

204225
### Planned
205226
- ⏳ ActiveHelp integration for contextual assistance
206-
- ⏳ Pre/post hooks (hooks.d folder execution)
207227
- ⏳ Enhanced directory help (show all subcommands in tree)
208228
- ⏳ Improved completion output filtering
209229

@@ -318,6 +338,7 @@ Make sure:
318338
- [Your First Script](#your-first-script) - Create your first script
319339
- [Writing Scripts Guide](./docs/writing-scripts.md) - Comprehensive guide to writing scripts
320340
- [Completion Guide](./docs/completion-guide.md) - Implement custom tab completions
341+
- [Pre-Run Hooks Guide](./docs/hooks.md) - Add validation and setup hooks
321342
- [Migration Guide](./docs/migration.md) - Migrate from original tome/sub
322343

323344
### Core Documentation

cmd/exec.go

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,44 @@ func ExecRunE(cmd *cobra.Command, args []string) error {
8080
envs = append(envs, fmt.Sprintf("%s_ROOT=%s", executableAsEnvPrefix, absRootDir))
8181
envs = append(envs, fmt.Sprintf("%s_EXECUTABLE=%s", executableAsEnvPrefix, config.ExecutableName()))
8282

83-
args = append([]string{maybeFile}, maybeArgs...)
84-
execOrLog(maybeFile, args, envs)
83+
// Check for hooks and generate wrapper if needed
84+
var execTarget string
85+
var execArgs []string
86+
87+
if !skipHooks {
88+
hookRunner := NewHookRunner(config)
89+
hooks, err := hookRunner.DiscoverHooks()
90+
if err != nil {
91+
fmt.Printf("Error discovering hooks: %v\n", err)
92+
os.Exit(1)
93+
}
94+
95+
if len(hooks) > 0 {
96+
// Generate wrapper script
97+
wrapperPath, err := hookRunner.GenerateWrapperScript(hooks, executable, maybeArgs)
98+
if err != nil {
99+
fmt.Printf("Error generating wrapper script: %v\n", err)
100+
os.Exit(1)
101+
}
102+
103+
// Clean up wrapper on exit (best effort)
104+
defer os.Remove(wrapperPath)
105+
106+
// Execute wrapper instead of script directly
107+
execTarget = wrapperPath
108+
execArgs = []string{wrapperPath}
109+
} else {
110+
// No hooks, execute script directly
111+
execTarget = executable
112+
execArgs = append([]string{executable}, maybeArgs...)
113+
}
114+
} else {
115+
// Skip hooks, execute script directly
116+
execTarget = executable
117+
execArgs = append([]string{executable}, maybeArgs...)
118+
}
119+
120+
execOrLog(execTarget, execArgs, envs)
85121
return nil
86122
}
87123

@@ -128,9 +164,12 @@ var execCmd = &cobra.Command{
128164
}
129165

130166
var dryRun bool
167+
var skipHooks bool
131168

132169
func init() {
133170
execCmd.Flags().BoolVar(&dryRun, "dry-run", false, "Dry run the exec command")
171+
execCmd.Flags().BoolVar(&skipHooks, "skip-hooks", false, "Skip pre-execution hooks")
134172
viper.BindPFlag("dry-run", execCmd.Flags().Lookup("dry-run"))
173+
viper.BindPFlag("skip-hooks", execCmd.Flags().Lookup("skip-hooks"))
135174
rootCmd.AddCommand(execCmd)
136175
}

0 commit comments

Comments
 (0)