@alexgorbatchev/dotfiles 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +397 -0
- package/cli-fj2hdbnx.js +5 -0
- package/cli-fj2hdbnx.js.map +9 -0
- package/cli-w822cqdk.js +4 -0
- package/cli-w822cqdk.js.map +10 -0
- package/cli.js +449 -0
- package/cli.js.map +283 -0
- package/dashboard-0ebz5sqb.js +159 -0
- package/dashboard-0ebz5sqb.js.map +102 -0
- package/dashboard-3axqywva.css +1 -0
- package/dashboard.js +13 -0
- package/package.json +63 -0
- package/prerender-kpxyx916.js +3 -0
- package/prerender-kpxyx916.js.map +11 -0
- package/schemas.d.ts +2730 -0
- package/skill/SKILL.md +74 -0
- package/skill/references/api-reference.md +614 -0
- package/skill/references/configuration.md +1154 -0
- package/skill/references/installation-methods/brew.md +62 -0
- package/skill/references/installation-methods/cargo.md +86 -0
- package/skill/references/installation-methods/curl-binary.md +73 -0
- package/skill/references/installation-methods/curl-script.md +132 -0
- package/skill/references/installation-methods/curl-tar.md +58 -0
- package/skill/references/installation-methods/dmg.md +113 -0
- package/skill/references/installation-methods/gitea-release.md +106 -0
- package/skill/references/installation-methods/github-release.md +97 -0
- package/skill/references/installation-methods/manual.md +74 -0
- package/skill/references/installation-methods/npm.md +75 -0
- package/skill/references/installation-methods/overview.md +293 -0
- package/skill/references/installation-methods/zsh-plugin.md +156 -0
- package/skill/references/make-tool.md +866 -0
- package/skill/references/shell-and-hooks.md +833 -0
- package/tool-types.d.ts +14 -0
- package/wasm-n3cagcre.js +3 -0
- package/wasm-n3cagcre.js.map +10 -0
|
@@ -0,0 +1,833 @@
|
|
|
1
|
+
# Shell Integration and Hooks
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Shell Integration](#shell-integration)
|
|
6
|
+
- [Shell Methods](#shell-methods)
|
|
7
|
+
- [Configurator Methods](#configurator-methods)
|
|
8
|
+
- [Basic Example](#basic-example)
|
|
9
|
+
- [PATH Modifications](#path-modifications)
|
|
10
|
+
- [Shell Functions](#shell-functions)
|
|
11
|
+
- [Sourcing Files and Functions](#sourcing-files-and-functions)
|
|
12
|
+
- [Script Timing](#script-timing)
|
|
13
|
+
- [Cross-Shell Configuration](#cross-shell-configuration)
|
|
14
|
+
- [Path References](#path-references)
|
|
15
|
+
- [Best Practices](#best-practices)
|
|
16
|
+
- [Symbolic Links](#symbolic-links)
|
|
17
|
+
- [Command Completions](#command-completions)
|
|
18
|
+
- [Configuration Options](#configuration-options)
|
|
19
|
+
- [Shell Callback Context](#shell-callback-context)
|
|
20
|
+
- [Static Completions (source)](#static-completions-source)
|
|
21
|
+
- [URL-Based Completions](#url-based-completions)
|
|
22
|
+
- [Dynamic Completions (cmd)](#dynamic-completions-cmd)
|
|
23
|
+
- [Binary Name Override](#binary-name-override)
|
|
24
|
+
- [CLI Completions](#cli-completions)
|
|
25
|
+
- [Hooks](#hooks)
|
|
26
|
+
- [Basic Usage](#basic-usage)
|
|
27
|
+
- [Hook Events](#hook-events)
|
|
28
|
+
- [Context Properties](#context-properties)
|
|
29
|
+
- [Examples](#examples)
|
|
30
|
+
- [Error Handling](#error-handling)
|
|
31
|
+
- [Environment Variables in Installation](#environment-variables-in-installation)
|
|
32
|
+
- [Best Practices](#best-practices-1)
|
|
33
|
+
- [Hook Execution Order](#hook-execution-order)
|
|
34
|
+
- [Complete Example](#complete-example)
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
# Shell Integration
|
|
39
|
+
|
|
40
|
+
Configure shell environments, aliases, completions, and functions.
|
|
41
|
+
|
|
42
|
+
## Shell Methods
|
|
43
|
+
|
|
44
|
+
| Method | Shell |
|
|
45
|
+
| ----------------------- | ---------- |
|
|
46
|
+
| `.zsh(callback)` | Zsh |
|
|
47
|
+
| `.bash(callback)` | Bash |
|
|
48
|
+
| `.powershell(callback)` | PowerShell |
|
|
49
|
+
|
|
50
|
+
Each callback receives:
|
|
51
|
+
|
|
52
|
+
- `shell` - Shell configurator for setting up environment, aliases, completions, etc.
|
|
53
|
+
- `ctx` - Context with `version` property (only available after installation)
|
|
54
|
+
|
|
55
|
+
For other context properties (`toolDir`, `currentDir`, `projectConfig`, etc.), use the outer `ctx` from `defineTool`.
|
|
56
|
+
|
|
57
|
+
## Configurator Methods
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
.zsh((shell) =>
|
|
61
|
+
shell
|
|
62
|
+
.env({ VAR: 'value' }) // Environment variables (PATH prohibited)
|
|
63
|
+
.path('$HOME/.local/bin') // Add directory to PATH
|
|
64
|
+
.aliases({ t: 'tool' }) // Shell aliases
|
|
65
|
+
.functions({ myFunc: 'cmd' }) // Shell functions
|
|
66
|
+
.completions('completions/_tool') // Completion file path
|
|
67
|
+
.sourceFile('shell/init.zsh') // Source a file (skips if missing)
|
|
68
|
+
.sourceFunction('myFunc') // Source output of a function (source <(myFunc))
|
|
69
|
+
.source('tool env --shell zsh') // Source output of inline shell code
|
|
70
|
+
.always(`eval "$(tool init)"`) // Run every shell startup
|
|
71
|
+
.once(`tool gen-completions`) // Run once after install
|
|
72
|
+
)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Basic Example
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
export default defineTool((install, ctx) =>
|
|
79
|
+
install('github-release', { repo: 'owner/tool' })
|
|
80
|
+
.bin('tool')
|
|
81
|
+
.zsh((shell) =>
|
|
82
|
+
shell
|
|
83
|
+
.env({ TOOL_HOME: ctx.currentDir })
|
|
84
|
+
.path(`${ctx.currentDir}/bin`) // Add tool's bin directory to PATH
|
|
85
|
+
.aliases({ t: 'tool', ts: 'tool status' })
|
|
86
|
+
.completions('completions/_tool')
|
|
87
|
+
.functions({
|
|
88
|
+
'tool-helper': 'tool --config "$TOOL_HOME/config.toml" "$@"',
|
|
89
|
+
})
|
|
90
|
+
)
|
|
91
|
+
);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## PATH Modifications
|
|
95
|
+
|
|
96
|
+
### `.path()` - Add Directory to PATH
|
|
97
|
+
|
|
98
|
+
Add a directory to the PATH environment variable. Paths are deduplicated during shell init generation.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
.zsh((shell) =>
|
|
102
|
+
shell
|
|
103
|
+
.path('$HOME/.local/bin') // Static path with shell variable
|
|
104
|
+
.path(`${ctx.currentDir}/bin`) // Dynamic path using context
|
|
105
|
+
)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Why use `.path()` instead of `.env({ PATH: ... })`?**
|
|
109
|
+
|
|
110
|
+
- Paths are automatically deduplicated across all tools
|
|
111
|
+
- Proper ordering is maintained (prepended to PATH by default)
|
|
112
|
+
- TypeScript prevents using `PATH` in `.env()` with a clear error message
|
|
113
|
+
|
|
114
|
+
**Note**: Setting `PATH` via `.env({ PATH: '...' })` is prohibited. Use `.path()` instead.
|
|
115
|
+
|
|
116
|
+
## Shell Functions
|
|
117
|
+
|
|
118
|
+
### `.functions()` - Define Shell Functions
|
|
119
|
+
|
|
120
|
+
Define shell functions that are generated into the shell init file.
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
.zsh((shell) =>
|
|
124
|
+
shell.functions({
|
|
125
|
+
'my-command': 'echo "Hello, world!"',
|
|
126
|
+
'tool-setup': 'cd /some/path && ./setup.sh',
|
|
127
|
+
})
|
|
128
|
+
)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Generated output:**
|
|
132
|
+
|
|
133
|
+
```zsh
|
|
134
|
+
my-command() {
|
|
135
|
+
echo "Hello, world!"
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
tool-setup() {
|
|
139
|
+
cd /some/path && ./setup.sh
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
This is useful for defining wrapper functions or custom commands.
|
|
144
|
+
|
|
145
|
+
## Sourcing Files and Functions
|
|
146
|
+
|
|
147
|
+
### `.sourceFile()` - Source a Script File
|
|
148
|
+
|
|
149
|
+
Source a script file during shell initialization. If the file doesn't exist, it's silently skipped.
|
|
150
|
+
The file is sourced in a way that respects the configured HOME directory while still affecting the current shell.
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
.zsh((shell) =>
|
|
154
|
+
shell
|
|
155
|
+
.sourceFile('init.zsh') // Relative to toolDir
|
|
156
|
+
.sourceFile(`${ctx.currentDir}/shell.zsh`) // Absolute path for installed archives
|
|
157
|
+
)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
- **Relative paths** -> resolve to `toolDir` (directory containing `.tool.ts`)
|
|
161
|
+
- **Absolute paths** -> used as-is
|
|
162
|
+
- File existence is checked before sourcing
|
|
163
|
+
|
|
164
|
+
**Generated output (zsh/bash):**
|
|
165
|
+
|
|
166
|
+
```zsh
|
|
167
|
+
__dotfiles_source_mytool_0() {
|
|
168
|
+
[[ -f "/path/to/init.zsh" ]] && cat "/path/to/init.zsh"
|
|
169
|
+
}
|
|
170
|
+
source <(__dotfiles_source_mytool_0)
|
|
171
|
+
unset -f __dotfiles_source_mytool_0
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The function is automatically cleaned up after sourcing to avoid shell pollution.
|
|
175
|
+
|
|
176
|
+
### `.sourceFunction()` - Source Function Output
|
|
177
|
+
|
|
178
|
+
Source the output of a shell function defined via `.functions()`. This is ideal for tools requiring dynamic initialization (e.g., `eval "$(tool init)"`).
|
|
179
|
+
|
|
180
|
+
**Important**: When a function is used with `.sourceFunction()`, its body must **output shell code to stdout**. This output is then sourced (executed) in the current shell. Common tools like `fnm`, `pyenv`, `rbenv`, and `zoxide` have commands that print shell code for this purpose.
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
.zsh((shell) =>
|
|
184
|
+
shell
|
|
185
|
+
.functions({
|
|
186
|
+
// fnm env --use-on-cd PRINTS shell code like:
|
|
187
|
+
// export FNM_DIR="/Users/me/.fnm"
|
|
188
|
+
// export PATH="...fnm/bin:$PATH"
|
|
189
|
+
initFnm: 'fnm env --use-on-cd',
|
|
190
|
+
})
|
|
191
|
+
.sourceFunction('initFnm')
|
|
192
|
+
)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Generated output (zsh/bash):**
|
|
196
|
+
|
|
197
|
+
```zsh
|
|
198
|
+
initFnm() {
|
|
199
|
+
fnm env --use-on-cd
|
|
200
|
+
}
|
|
201
|
+
source <(initFnm)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Key differences from `.always()`:**
|
|
205
|
+
|
|
206
|
+
- `.sourceFunction()` emits `source <(fnName)` directly without any wrapping
|
|
207
|
+
- The function's stdout is sourced as shell code, running in the current shell
|
|
208
|
+
- Type-safe: only accepts function names defined via `.functions()`
|
|
209
|
+
|
|
210
|
+
### `.source()` - Source Inline Shell Code Output
|
|
211
|
+
|
|
212
|
+
Source the output of inline shell code without defining a named function. The content must **print shell code to stdout** - this output is then sourced.
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
.zsh((shell) =>
|
|
216
|
+
shell
|
|
217
|
+
// fnm env prints shell code like "export PATH=..."
|
|
218
|
+
.source('fnm env --use-on-cd')
|
|
219
|
+
// Or echo shell code directly
|
|
220
|
+
.source('echo "export MY_VAR=value"')
|
|
221
|
+
)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Generated output (zsh/bash):**
|
|
225
|
+
|
|
226
|
+
```zsh
|
|
227
|
+
__dotfiles_source_mytool_0() {
|
|
228
|
+
fnm env --use-on-cd
|
|
229
|
+
}
|
|
230
|
+
source <(__dotfiles_source_mytool_0)
|
|
231
|
+
unset -f __dotfiles_source_mytool_0
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Use `.source()` when:
|
|
235
|
+
|
|
236
|
+
- You need to source command output inline without a named function
|
|
237
|
+
- The command prints shell code that should be executed in the current shell
|
|
238
|
+
- You don't need to call the function by name elsewhere
|
|
239
|
+
|
|
240
|
+
For reusable functions, use `.functions()` + `.sourceFunction()` instead.
|
|
241
|
+
|
|
242
|
+
## Script Timing
|
|
243
|
+
|
|
244
|
+
### `.always()` - Every Shell Startup
|
|
245
|
+
|
|
246
|
+
For fast inline operations that run on every shell startup:
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
.zsh((shell) =>
|
|
250
|
+
shell.always(`
|
|
251
|
+
eval "$(tool init zsh)"
|
|
252
|
+
`)
|
|
253
|
+
)
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### `.once()` - After Installation
|
|
257
|
+
|
|
258
|
+
For expensive operations:
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
export default defineTool((install, ctx) =>
|
|
262
|
+
install('github-release', { repo: 'owner/tool' })
|
|
263
|
+
.bin('tool')
|
|
264
|
+
.zsh((shell) =>
|
|
265
|
+
shell.once(`
|
|
266
|
+
tool gen-completions --zsh > "${ctx.projectConfig.paths.generatedDir}/completions/_tool"
|
|
267
|
+
`)
|
|
268
|
+
)
|
|
269
|
+
);
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Cross-Shell Configuration
|
|
273
|
+
|
|
274
|
+
Share configuration across shells using the outer `ctx` from `defineTool`:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
export default defineTool((install, ctx) => {
|
|
278
|
+
const configureShell = (shell) => shell.env({ TOOL_HOME: ctx.currentDir }).aliases({ t: 'tool' });
|
|
279
|
+
|
|
280
|
+
return install('github-release', { repo: 'owner/tool' }).bin('tool').zsh(configureShell).bash(configureShell);
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Path References
|
|
285
|
+
|
|
286
|
+
Always use context variables from the outer `ctx`:
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
export default defineTool((install, ctx) =>
|
|
290
|
+
install('github-release', { repo: 'owner/tool' })
|
|
291
|
+
.bin('tool')
|
|
292
|
+
.zsh((shell) =>
|
|
293
|
+
shell.env({
|
|
294
|
+
TOOL_CONFIG: ctx.toolDir, // Tool config directory
|
|
295
|
+
TOOL_DATA: '~/.local/share/tool',
|
|
296
|
+
}).always(`
|
|
297
|
+
FZF_DIR="${ctx.projectConfig.paths.binariesDir}/fzf"
|
|
298
|
+
[[ -d "$FZF_DIR" ]] && export FZF_BASE="$FZF_DIR"
|
|
299
|
+
`)
|
|
300
|
+
)
|
|
301
|
+
);
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## Best Practices
|
|
305
|
+
|
|
306
|
+
- Use declarative methods (`.env()`, `.aliases()`) for simple config
|
|
307
|
+
- Use `.always()` for fast runtime setup only
|
|
308
|
+
- Use `.once()` for expensive operations (completion generation, cache building)
|
|
309
|
+
- Use context variables for all paths - never hardcode
|
|
310
|
+
|
|
311
|
+
## Symbolic Links
|
|
312
|
+
|
|
313
|
+
Create symlinks for configuration files with `.symlink()`.
|
|
314
|
+
|
|
315
|
+
### Syntax
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
.symlink(source, target)
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
| Parameter | Description |
|
|
322
|
+
| --------- | ------------------------------------------------------------------------ |
|
|
323
|
+
| `source` | Path to source file/directory. `./` is relative to tool config directory |
|
|
324
|
+
| `target` | Absolute path for symlink. Use context variables or `~` |
|
|
325
|
+
|
|
326
|
+
### Path Resolution
|
|
327
|
+
|
|
328
|
+
| Source | Resolution |
|
|
329
|
+
| ---------------- | -------------------------------- |
|
|
330
|
+
| `./config.toml` | Relative to `.tool.ts` directory |
|
|
331
|
+
| `/etc/tool.conf` | Absolute path |
|
|
332
|
+
|
|
333
|
+
| Target | Resolution |
|
|
334
|
+
| ---------------- | ---------------------------------------------- |
|
|
335
|
+
| `~/.config/tool` | Expanded automatically via home path expansion |
|
|
336
|
+
|
|
337
|
+
### Example
|
|
338
|
+
|
|
339
|
+
```
|
|
340
|
+
tools/my-tool/
|
|
341
|
+
├── my-tool.tool.ts
|
|
342
|
+
├── config.toml
|
|
343
|
+
└── themes/
|
|
344
|
+
├── dark.toml
|
|
345
|
+
└── light.toml
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
export default defineTool((install) =>
|
|
350
|
+
install('github-release', { repo: 'owner/my-tool' })
|
|
351
|
+
.bin('my-tool')
|
|
352
|
+
.symlink('./config.toml', '~/.config/my-tool/config.toml')
|
|
353
|
+
.symlink('./themes/', '~/.config/my-tool/themes')
|
|
354
|
+
.zsh((shell) =>
|
|
355
|
+
shell.env({
|
|
356
|
+
MY_TOOL_CONFIG: '~/.config/my-tool/config.toml',
|
|
357
|
+
})
|
|
358
|
+
)
|
|
359
|
+
);
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Common Patterns
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
// Configuration files
|
|
366
|
+
.symlink('./gitconfig', '~/.gitconfig')
|
|
367
|
+
|
|
368
|
+
// Directories
|
|
369
|
+
.symlink('./themes/', '~/.config/tool/themes')
|
|
370
|
+
|
|
371
|
+
// Scripts
|
|
372
|
+
.symlink('./scripts/helper.sh', '~/bin/helper')
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Correct vs Incorrect
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
// ✅ Tilde expansion (recommended)
|
|
379
|
+
.symlink('./config.toml', '~/.config/tool/config.toml')
|
|
380
|
+
|
|
381
|
+
// ❌ Hardcoded path
|
|
382
|
+
.symlink('./config.toml', '/home/user/.config/tool/config.toml')
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
# Command Completions
|
|
388
|
+
|
|
389
|
+
Tab completions are configured per-shell using `.completions()`:
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
.zsh((shell) => shell.completions('completions/_tool.zsh'))
|
|
393
|
+
.bash((shell) => shell.completions('completions/tool.bash'))
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
> **Lifecycle**: All completions are generated only after `dotfiles install <tool>` succeeds,
|
|
397
|
+
> not during `dotfiles generate`. This ensures cmd-based completions can execute the installed
|
|
398
|
+
> binary and callbacks receive the actual installed version in `ctx.version`.
|
|
399
|
+
|
|
400
|
+
## Configuration Options
|
|
401
|
+
|
|
402
|
+
| Property | Description |
|
|
403
|
+
| -------- | ---------------------------------------------------------------------------------------- |
|
|
404
|
+
| `source` | Path to completion file (relative to toolDir, or absolute path within extracted archive) |
|
|
405
|
+
| `url` | URL to download completion file or archive from |
|
|
406
|
+
| `cmd` | Command to generate completions dynamically |
|
|
407
|
+
| `bin` | Binary name for completion filename (when different from tool name) |
|
|
408
|
+
|
|
409
|
+
**Note**: Use one of these combinations:
|
|
410
|
+
|
|
411
|
+
- `'_tool.zsh'` - String path (relative to toolDir or absolute)
|
|
412
|
+
- `{ source }` - Static file (relative to toolDir or absolute)
|
|
413
|
+
- `{ cmd }` - Generate dynamically by running a command
|
|
414
|
+
- `{ url }` - Download direct completion file from URL (filename derived from URL)
|
|
415
|
+
- `{ url, source }` - Download archive, extract, use source as path to file within
|
|
416
|
+
|
|
417
|
+
## Shell Callback Context
|
|
418
|
+
|
|
419
|
+
The shell callback receives two parameters:
|
|
420
|
+
|
|
421
|
+
- `shell` - The shell configurator for setting up completions, aliases, etc.
|
|
422
|
+
- `ctx` - Context with `version` property (only available after installation)
|
|
423
|
+
|
|
424
|
+
For other context properties (`toolDir`, `currentDir`, `projectConfig`, etc.), use the outer `ctx` from `defineTool`.
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
export default defineTool((install, ctx) =>
|
|
428
|
+
install('github-release', { repo: 'owner/tool' })
|
|
429
|
+
.bin('tool')
|
|
430
|
+
.zsh((shell) => shell.completions('completions/_tool.zsh'))
|
|
431
|
+
);
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
## Static Completions (source)
|
|
435
|
+
|
|
436
|
+
For completion files bundled in tool archives:
|
|
437
|
+
|
|
438
|
+
```typescript
|
|
439
|
+
// Simple path relative to extracted archive
|
|
440
|
+
.zsh((shell) => shell.completions('completions/_tool.zsh'))
|
|
441
|
+
|
|
442
|
+
// Glob pattern for versioned directories
|
|
443
|
+
.zsh((shell) => shell.completions('*/complete/_rg'))
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
**Supported glob patterns**: `*`, `**`, `?`, `[abc]`
|
|
447
|
+
|
|
448
|
+
## URL-Based Completions
|
|
449
|
+
|
|
450
|
+
For downloading completions from external sources. Supports both direct files and archives.
|
|
451
|
+
|
|
452
|
+
### Direct File Download
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
// Direct completion file download (source is optional - derived from URL)
|
|
456
|
+
.zsh((shell) => shell.completions({
|
|
457
|
+
url: 'https://raw.githubusercontent.com/user/repo/main/completions/_tool'
|
|
458
|
+
}))
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### Archive Download
|
|
462
|
+
|
|
463
|
+
```typescript
|
|
464
|
+
// Archive download with source path to file within
|
|
465
|
+
.zsh((shell) => shell.completions({
|
|
466
|
+
url: 'https://github.com/user/repo/releases/download/v1.0/completions.tar.gz',
|
|
467
|
+
source: `${ctx.currentDir}/completions/_tool.zsh`
|
|
468
|
+
}))
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
**Note**: For archives, `source` specifies the absolute path to the completion file within the extracted archive. For direct files, `source` is optional - the filename is derived from the URL.
|
|
472
|
+
|
|
473
|
+
### Version-Dependent URLs (Callback)
|
|
474
|
+
|
|
475
|
+
For completions that need the installed version in the URL, use a callback:
|
|
476
|
+
|
|
477
|
+
```typescript
|
|
478
|
+
// Direct file with version in URL
|
|
479
|
+
.zsh((shell) => shell.completions((ctx) => ({
|
|
480
|
+
url: `https://raw.githubusercontent.com/user/repo/${ctx.version}/completions/_tool`
|
|
481
|
+
})))
|
|
482
|
+
|
|
483
|
+
// Archive with version in URL (requires source)
|
|
484
|
+
.zsh((shell) => shell.completions((ctx) => ({
|
|
485
|
+
url: `https://github.com/user/repo/releases/download/${ctx.version}/completions.tar.gz`,
|
|
486
|
+
source: `${ctx.currentDir}/completions/_tool.zsh`
|
|
487
|
+
})))
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
The callback receives `ctx` with:
|
|
491
|
+
|
|
492
|
+
- `version` - The installed version of the tool (e.g., `'v10.3.0'`, `'15.1.0'`), only available after installation completes
|
|
493
|
+
|
|
494
|
+
URL-based completions are downloaded to `ctx.currentDir`. For archives, they are automatically extracted and `source` specifies the path to the completion file within.
|
|
495
|
+
|
|
496
|
+
**Supported archive formats**: `.tar.gz`, `.tar.xz`, `.tar.bz2`, `.zip`, `.tar`, `.tar.lzma`, `.7z`
|
|
497
|
+
|
|
498
|
+
## Dynamic Completions (cmd)
|
|
499
|
+
|
|
500
|
+
For tools that generate completions at runtime (recommended for version-dependent completions):
|
|
501
|
+
|
|
502
|
+
```typescript
|
|
503
|
+
.zsh((shell) => shell.completions({ cmd: 'tool completion zsh' }))
|
|
504
|
+
.bash((shell) => shell.completions({ cmd: 'tool completion bash' }))
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
## Binary Name Override
|
|
508
|
+
|
|
509
|
+
When tool filename differs from binary name (e.g., `curl-script--fnm.tool.ts` for binary `fnm`):
|
|
510
|
+
|
|
511
|
+
```typescript
|
|
512
|
+
.zsh((shell) => shell.completions({
|
|
513
|
+
cmd: 'fnm completions --shell zsh',
|
|
514
|
+
bin: 'fnm' // Results in '_fnm' instead of '_curl-script--fnm'
|
|
515
|
+
}))
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
## CLI Completions
|
|
519
|
+
|
|
520
|
+
The CLI generates its own completions to `<generatedDir>/shell-scripts/zsh/completions/_dotfiles`. Commands that accept tool names include all configured tools in their completions.
|
|
521
|
+
|
|
522
|
+
Reload completions after running `dotfiles generate`:
|
|
523
|
+
|
|
524
|
+
```bash
|
|
525
|
+
autoload -U compinit && compinit
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
---
|
|
529
|
+
|
|
530
|
+
# Hooks
|
|
531
|
+
|
|
532
|
+
Hooks allow custom logic at different stages of the installation process.
|
|
533
|
+
|
|
534
|
+
## Basic Usage
|
|
535
|
+
|
|
536
|
+
```typescript
|
|
537
|
+
import { defineTool } from '@alexgorbatchev/dotfiles';
|
|
538
|
+
|
|
539
|
+
export default defineTool((install, ctx) =>
|
|
540
|
+
install('github-release', { repo: 'owner/tool' })
|
|
541
|
+
.bin('tool')
|
|
542
|
+
.hook('after-install', async (context) => {
|
|
543
|
+
const { $, log, fileSystem } = context;
|
|
544
|
+
await $`./tool init`;
|
|
545
|
+
log.info('Tool initialized');
|
|
546
|
+
})
|
|
547
|
+
);
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
## Hook Events
|
|
551
|
+
|
|
552
|
+
| Event | When | Available Properties |
|
|
553
|
+
| ---------------- | ---------------------------- | ------------------------------------------ |
|
|
554
|
+
| `before-install` | Before installation starts | `stagingDir` |
|
|
555
|
+
| `after-download` | After file download | `stagingDir`, `downloadPath` |
|
|
556
|
+
| `after-extract` | After archive extraction | `stagingDir`, `downloadPath`, `extractDir` |
|
|
557
|
+
| `after-install` | After installation completes | `installedDir`, `binaryPaths`, `version` |
|
|
558
|
+
|
|
559
|
+
## Context Properties
|
|
560
|
+
|
|
561
|
+
All hooks receive a context object with:
|
|
562
|
+
|
|
563
|
+
| Property | Description |
|
|
564
|
+
| --------------- | ---------------------------------------------------- |
|
|
565
|
+
| `toolName` | Name of the tool |
|
|
566
|
+
| `currentDir` | Stable path (symlink) for this tool |
|
|
567
|
+
| `stagingDir` | Temporary installation directory |
|
|
568
|
+
| `systemInfo` | Platform, architecture, home directory |
|
|
569
|
+
| `fileSystem` | File operations (mkdir, writeFile, exists, etc.) |
|
|
570
|
+
| `replaceInFile` | Regex-based file text replacement |
|
|
571
|
+
| `log` | Structured logging (trace, debug, info, warn, error) |
|
|
572
|
+
| `projectConfig` | Project configuration |
|
|
573
|
+
| `toolConfig` | Tool configuration |
|
|
574
|
+
| `$` | Bun shell executor |
|
|
575
|
+
|
|
576
|
+
> **Note:** The `stagingDir` and `projectConfig` properties form the base environment context (`IEnvContext`) that is also available to dynamic `env` functions in install parameters.
|
|
577
|
+
|
|
578
|
+
## Examples
|
|
579
|
+
|
|
580
|
+
### File Operations
|
|
581
|
+
|
|
582
|
+
```typescript
|
|
583
|
+
.hook('after-install', async ({ fileSystem, systemInfo, log }) => {
|
|
584
|
+
const configDir = `${systemInfo.homeDir}/.config/tool`;
|
|
585
|
+
await fileSystem.mkdir(configDir, { recursive: true });
|
|
586
|
+
await fileSystem.writeFile(`${configDir}/config.toml`, 'theme = "dark"');
|
|
587
|
+
log.info('Configuration created');
|
|
588
|
+
})
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### Shell Commands
|
|
592
|
+
|
|
593
|
+
```typescript
|
|
594
|
+
.hook('after-install', async ({ $, installedDir }) => {
|
|
595
|
+
// Run tool command
|
|
596
|
+
await $`${installedDir}/tool init`;
|
|
597
|
+
|
|
598
|
+
// Capture output
|
|
599
|
+
const version = await $`./tool --version`.text();
|
|
600
|
+
})
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
### Executing Installed Binaries by Name
|
|
604
|
+
|
|
605
|
+
In `after-install` hooks, the shell's PATH is automatically enhanced to include the directories containing the installed binaries. This means you can execute freshly installed tools by name without specifying the full path:
|
|
606
|
+
|
|
607
|
+
```typescript
|
|
608
|
+
.hook('after-install', async ({ $ }) => {
|
|
609
|
+
// The installed binary is automatically available by name
|
|
610
|
+
await $`my-tool --version`;
|
|
611
|
+
|
|
612
|
+
// No need to use full paths like:
|
|
613
|
+
// await $`${installedDir}/bin/my-tool --version`;
|
|
614
|
+
})
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
This PATH enhancement only applies to `after-install` hooks where `binaryPaths` is available in the context.
|
|
618
|
+
|
|
619
|
+
### Shell Command Logging
|
|
620
|
+
|
|
621
|
+
Shell commands executed in hooks are automatically logged to help with debugging and visibility:
|
|
622
|
+
|
|
623
|
+
- Commands are logged as `$ command` at info level before execution
|
|
624
|
+
- Stdout lines are logged as `| line` at info level
|
|
625
|
+
- Stderr lines are logged as `| line` at error level (only if stderr has content)
|
|
626
|
+
|
|
627
|
+
Example output:
|
|
628
|
+
|
|
629
|
+
```
|
|
630
|
+
$ my-tool init
|
|
631
|
+
| Initializing configuration...
|
|
632
|
+
| Configuration complete!
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
This logging happens regardless of whether `.quiet()` is used on the shell command, since logging occurs at the hook executor level.
|
|
636
|
+
|
|
637
|
+
### Platform-Specific Setup
|
|
638
|
+
|
|
639
|
+
```typescript
|
|
640
|
+
.hook('after-install', async ({ systemInfo, $ }) => {
|
|
641
|
+
if (systemInfo.platform === 'darwin') {
|
|
642
|
+
await $`./setup-macos.sh`;
|
|
643
|
+
} else if (systemInfo.platform === 'linux') {
|
|
644
|
+
await $`./setup-linux.sh`;
|
|
645
|
+
}
|
|
646
|
+
})
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
### File Text Replacement
|
|
650
|
+
|
|
651
|
+
```typescript
|
|
652
|
+
.hook('after-install', async ({ replaceInFile, installedDir }) => {
|
|
653
|
+
// Replace a config value (returns true if replaced, false otherwise)
|
|
654
|
+
const wasReplaced = await replaceInFile(
|
|
655
|
+
`${installedDir}/config.toml`,
|
|
656
|
+
/theme = ".*"/,
|
|
657
|
+
'theme = "dark"'
|
|
658
|
+
);
|
|
659
|
+
|
|
660
|
+
// Increment version numbers line-by-line
|
|
661
|
+
await replaceInFile(
|
|
662
|
+
`${installedDir}/versions.txt`,
|
|
663
|
+
/version=(\d+)/,
|
|
664
|
+
(match) => `version=${Number(match.captures[0]) + 1}`,
|
|
665
|
+
{ mode: 'line' }
|
|
666
|
+
);
|
|
667
|
+
|
|
668
|
+
// Log error if pattern not found (helpful for debugging)
|
|
669
|
+
await replaceInFile(
|
|
670
|
+
`${installedDir}/config.toml`,
|
|
671
|
+
/api_key = ".*"/,
|
|
672
|
+
'api_key = "secret"',
|
|
673
|
+
{ errorMessage: 'Could not find api_key setting' }
|
|
674
|
+
);
|
|
675
|
+
})
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
### Build from Source
|
|
679
|
+
|
|
680
|
+
```typescript
|
|
681
|
+
.hook('after-extract', async ({ extractDir, stagingDir, $ }) => {
|
|
682
|
+
if (extractDir) {
|
|
683
|
+
await $`cd ${extractDir} && make build`;
|
|
684
|
+
await $`mv ${extractDir}/target/release/tool ${stagingDir}/tool`;
|
|
685
|
+
}
|
|
686
|
+
})
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
## Error Handling
|
|
690
|
+
|
|
691
|
+
```typescript
|
|
692
|
+
.hook('after-install', async ({ $, log }) => {
|
|
693
|
+
try {
|
|
694
|
+
await $`./tool self-test`;
|
|
695
|
+
} catch (error) {
|
|
696
|
+
log.error('Self-test failed');
|
|
697
|
+
throw error; // Re-throw to fail installation
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
### Custom Binary Processing
|
|
703
|
+
|
|
704
|
+
```typescript
|
|
705
|
+
import { defineTool } from '@alexgorbatchev/dotfiles';
|
|
706
|
+
import path from 'path';
|
|
707
|
+
|
|
708
|
+
export default defineTool((install, ctx) =>
|
|
709
|
+
install('github-release', { repo: 'owner/custom-tool' })
|
|
710
|
+
.bin('custom-tool')
|
|
711
|
+
.hook('after-extract', async ({ extractDir, stagingDir, fileSystem, log }) => {
|
|
712
|
+
if (extractDir) {
|
|
713
|
+
// Custom binary selection and processing
|
|
714
|
+
const binaries = await fileSystem.readdir(path.join(extractDir, 'bin'));
|
|
715
|
+
const mainBinary = binaries.find((name) => name.startsWith('main-'));
|
|
716
|
+
|
|
717
|
+
if (mainBinary) {
|
|
718
|
+
const sourcePath = path.join(extractDir, 'bin', mainBinary);
|
|
719
|
+
const targetPath = path.join(stagingDir ?? '', 'tool');
|
|
720
|
+
await fileSystem.copy(sourcePath, targetPath);
|
|
721
|
+
log.info(`Selected binary: ${mainBinary}`);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
})
|
|
725
|
+
);
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
### Environment-Specific Setup
|
|
729
|
+
|
|
730
|
+
```typescript
|
|
731
|
+
import { defineTool } from '@alexgorbatchev/dotfiles';
|
|
732
|
+
|
|
733
|
+
export default defineTool((install, ctx) =>
|
|
734
|
+
install('github-release', { repo: 'owner/custom-tool' })
|
|
735
|
+
.bin('custom-tool')
|
|
736
|
+
.hook('after-install', async ({ systemInfo, fileSystem, log, $ }) => {
|
|
737
|
+
// Platform-specific setup
|
|
738
|
+
if (systemInfo.platform === 'darwin') {
|
|
739
|
+
// macOS-specific setup
|
|
740
|
+
await $`./setup-macos.sh`;
|
|
741
|
+
} else if (systemInfo.platform === 'linux') {
|
|
742
|
+
// Linux-specific setup
|
|
743
|
+
await $`./setup-linux.sh`;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// Architecture-specific setup
|
|
747
|
+
if (systemInfo.arch === 'arm64') {
|
|
748
|
+
log.info('Configuring for ARM64 architecture');
|
|
749
|
+
await $`./configure-arm64.sh`;
|
|
750
|
+
}
|
|
751
|
+
})
|
|
752
|
+
);
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
## Environment Variables in Installation
|
|
756
|
+
|
|
757
|
+
Set environment variables during installation (for curl-script installs):
|
|
758
|
+
|
|
759
|
+
```typescript
|
|
760
|
+
import { defineTool } from '@alexgorbatchev/dotfiles';
|
|
761
|
+
|
|
762
|
+
export default defineTool((install) =>
|
|
763
|
+
install('curl-script', {
|
|
764
|
+
url: 'https://example.com/install.sh',
|
|
765
|
+
shell: 'bash',
|
|
766
|
+
env: {
|
|
767
|
+
INSTALL_DIR: '~/.local/bin',
|
|
768
|
+
ENABLE_FEATURE: 'true',
|
|
769
|
+
API_KEY: process.env.TOOL_API_KEY || 'default',
|
|
770
|
+
},
|
|
771
|
+
}).bin('my-tool')
|
|
772
|
+
);
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
## Best Practices
|
|
776
|
+
|
|
777
|
+
1. **Use `$` for shell operations** that need to work with files relative to your tool config
|
|
778
|
+
2. **Use `fileSystem` methods** for cross-platform file operations that don't require shell features
|
|
779
|
+
3. **Always handle errors appropriately** in hooks to provide clear feedback
|
|
780
|
+
4. **Use `log` for all output** - avoid `console.log()` in favor of structured logging:
|
|
781
|
+
- `log.info()` for general information
|
|
782
|
+
- `log.warn()` for warnings
|
|
783
|
+
- `log.error()` for error conditions
|
|
784
|
+
- `log.debug()` for debugging and troubleshooting
|
|
785
|
+
5. **Test your hooks** on different platforms to ensure compatibility
|
|
786
|
+
6. **Keep hooks focused** - each hook should have a single responsibility
|
|
787
|
+
7. **Document complex logic** - explain what your hooks are doing and why
|
|
788
|
+
|
|
789
|
+
## Hook Execution Order
|
|
790
|
+
|
|
791
|
+
1. **`beforeInstall`**: Before any installation steps
|
|
792
|
+
2. **`afterDownload`**: After downloading but before extraction
|
|
793
|
+
3. **`afterExtract`**: After extraction but before binary setup
|
|
794
|
+
4. **`afterInstall`**: After all installation steps are complete
|
|
795
|
+
|
|
796
|
+
## Complete Example
|
|
797
|
+
|
|
798
|
+
```typescript
|
|
799
|
+
import { defineTool } from '@alexgorbatchev/dotfiles';
|
|
800
|
+
import path from 'path';
|
|
801
|
+
|
|
802
|
+
export default defineTool((install, ctx) =>
|
|
803
|
+
install('github-release', { repo: 'owner/custom-tool' })
|
|
804
|
+
.bin('custom-tool')
|
|
805
|
+
.symlink('./config.yml', '~/.config/custom-tool/config.yml')
|
|
806
|
+
.hook('before-install', async ({ log }) => {
|
|
807
|
+
log.info('Starting custom-tool installation...');
|
|
808
|
+
})
|
|
809
|
+
.hook('after-extract', async ({ extractDir, log, $ }) => {
|
|
810
|
+
if (extractDir) {
|
|
811
|
+
// Build additional components
|
|
812
|
+
log.info('Building plugins...');
|
|
813
|
+
await $`cd ${extractDir} && make plugins`;
|
|
814
|
+
}
|
|
815
|
+
})
|
|
816
|
+
.hook('after-install', async ({ toolName, installedDir, systemInfo, fileSystem, log, $ }) => {
|
|
817
|
+
// Create data directory
|
|
818
|
+
const dataDir = path.join(systemInfo.homeDir, '.local/share', toolName);
|
|
819
|
+
await fileSystem.mkdir(dataDir, { recursive: true });
|
|
820
|
+
|
|
821
|
+
// Initialize tool
|
|
822
|
+
await $`${path.join(installedDir ?? '', toolName)} init --data-dir ${dataDir}`;
|
|
823
|
+
|
|
824
|
+
// Set up completion
|
|
825
|
+
await $`${
|
|
826
|
+
path.join(installedDir ?? '', toolName)
|
|
827
|
+
} completion zsh > ${ctx.projectConfig.paths.generatedDir}/completions/_${toolName}`;
|
|
828
|
+
|
|
829
|
+
log.info(`Initialized ${toolName} with data directory: ${dataDir}`);
|
|
830
|
+
})
|
|
831
|
+
.zsh((shell) => shell.env({ CUSTOM_TOOL_DATA: '~/.local/share/custom-tool' }).aliases({ ct: 'custom-tool' }))
|
|
832
|
+
);
|
|
833
|
+
```
|