Skip to content

Commit d31d3d1

Browse files
authored
feat: add fish.ts & bash.ts (#19)
* feat: add fish.ts & bash.ts * fix: format * cleanup: remove unused type definition * apply suggestions from @43081j review * fix: format * moar cleanup * fix: add missing serve command to demo.cac.ts to correct CLI test snapshots * fix: add serve command and use snapshot testing for shells - Add missing serve command to demo.cac.ts to correct CLI test snapshots - Implement suggestion to use toMatchSnapshot() for fish shell tests - Update snapshots to include proper completions
1 parent 820a3d6 commit d31d3d1

File tree

11 files changed

+1120
-217
lines changed

11 files changed

+1120
-217
lines changed

README.md

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ Shell autocompletions are largely missing in the javascript cli ecosystem. This
66

77
Tools like git and their autocompletion experience inspired us to build this tool and make the same ability available for any javascript cli project. Developers love hitting the tab key, hence why they prefer tabs over spaces.
88

9+
## Examples
10+
11+
Check out the [examples directory](./examples) for complete examples of using Tab with different command-line frameworks:
12+
13+
- [CAC](./examples/demo.cac.ts)
14+
- [Citty](./examples/demo.citty.ts)
15+
- [Commander.js](./examples/demo.commander.ts)
16+
17+
## Usage
18+
919
```ts
1020
import { Completion, script } from '@bombsh/tab';
1121

@@ -146,6 +156,47 @@ const cli = createMain(main);
146156
cli();
147157
```
148158

159+
### `@bombsh/tab/commander`
160+
161+
```ts
162+
import { Command } from 'commander';
163+
import tab from '@bombsh/tab/commander';
164+
165+
const program = new Command('my-cli');
166+
program.version('1.0.0');
167+
168+
// Add commands
169+
program
170+
.command('serve')
171+
.description('Start the server')
172+
.option('-p, --port <number>', 'port to use', '3000')
173+
.option('-H, --host <host>', 'host to use', 'localhost')
174+
.action((options) => {
175+
console.log('Starting server...');
176+
});
177+
178+
// Initialize tab completion
179+
const completion = tab(program);
180+
181+
// Configure custom completions
182+
for (const command of completion.commands.values()) {
183+
if (command.name === 'serve') {
184+
for (const [option, config] of command.options.entries()) {
185+
if (option === '--port') {
186+
config.handler = () => {
187+
return [
188+
{ value: '3000', description: 'Default port' },
189+
{ value: '8080', description: 'Alternative port' },
190+
];
191+
};
192+
}
193+
}
194+
}
195+
}
196+
197+
program.parse();
198+
```
199+
149200
## Recipe
150201

151202
`source <(my-cli complete zsh)` won't be enough since the user would have to run this command each time they spin up a new shell instance.
@@ -157,6 +208,20 @@ my-cli completion zsh > ~/completion-for-my-cli.zsh
157208
echo 'source ~/completion-for-my-cli.zsh' >> ~/.zshrc
158209
```
159210

211+
For other shells:
212+
213+
```bash
214+
# Bash
215+
my-cli complete bash > ~/.bash_completion.d/my-cli
216+
echo 'source ~/.bash_completion.d/my-cli' >> ~/.bashrc
217+
218+
# Fish
219+
my-cli complete fish > ~/.config/fish/completions/my-cli.fish
220+
221+
# PowerShell
222+
my-cli complete powershell > $PROFILE.CurrentUserAllHosts
223+
```
224+
160225
## Autocompletion Server
161226

162227
By integrating tab into your cli, your cli would have a new command called `complete`. This is where all the magic happens. And the shell would contact this command to get completions. That's why we call it the autocompletion server.
@@ -181,8 +246,3 @@ Other package managers like `npm` and `yarn` can decide whether to support this
181246

182247
- git
183248
- [cobra](https://github.com/spf13/cobra/blob/main/shell_completions.go), without cobra, tab would have took 10x longer to build
184-
185-
## TODO
186-
187-
- [] fish
188-
- [] bash

demo.citty.ts

Lines changed: 0 additions & 128 deletions
This file was deleted.

demo.cac.ts renamed to examples/demo.cac.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import cac from 'cac';
2-
import tab from './src/cac';
2+
import tab from '../src/cac';
33

44
const cli = cac('vite');
55

@@ -14,6 +14,12 @@ cli
1414
.option('-p, --port <port>', `Specify port`)
1515
.action((options) => {});
1616

17+
cli
18+
.command('serve', 'Start the server')
19+
.option('-H, --host [host]', `Specify hostname`)
20+
.option('-p, --port <port>', `Specify port`)
21+
.action((options) => {});
22+
1723
cli.command('dev build', 'Build project').action((options) => {});
1824

1925
cli.command('lint [...files]', 'Lint project').action((files, options) => {});

examples/demo.citty.ts

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { defineCommand, createMain, CommandDef, ArgsDef } from 'citty';
2+
import tab from '../src/citty';
3+
4+
const main = defineCommand({
5+
meta: {
6+
name: 'vite',
7+
version: '0.0.0',
8+
description: 'Vite CLI',
9+
},
10+
args: {
11+
config: {
12+
type: 'string',
13+
description: 'Use specified config file',
14+
alias: 'c',
15+
},
16+
mode: {
17+
type: 'string',
18+
description: 'Set env mode',
19+
alias: 'm',
20+
},
21+
logLevel: {
22+
type: 'string',
23+
description: 'info | warn | error | silent',
24+
alias: 'l',
25+
},
26+
},
27+
run: (_ctx) => {},
28+
});
29+
30+
const devCommand = defineCommand({
31+
meta: {
32+
name: 'dev',
33+
description: 'Start dev server',
34+
},
35+
args: {
36+
host: {
37+
type: 'string',
38+
description: 'Specify hostname',
39+
alias: 'H',
40+
},
41+
port: {
42+
type: 'string',
43+
description: 'Specify port',
44+
alias: 'p',
45+
},
46+
},
47+
run: () => {},
48+
});
49+
50+
const buildCommand = defineCommand({
51+
meta: {
52+
name: 'build',
53+
description: 'Build project',
54+
},
55+
run: () => {},
56+
});
57+
58+
const lintCommand = defineCommand({
59+
meta: {
60+
name: 'lint',
61+
description: 'Lint project',
62+
},
63+
args: {
64+
files: {
65+
type: 'positional',
66+
description: 'Files to lint',
67+
required: false,
68+
},
69+
},
70+
run: () => {},
71+
});
72+
73+
main.subCommands = {
74+
dev: devCommand,
75+
build: buildCommand,
76+
lint: lintCommand,
77+
} as Record<string, CommandDef<ArgsDef>>;
78+
79+
const completion = await tab(main, {
80+
options: {
81+
config: {
82+
handler: () => [
83+
{ value: 'vite.config.ts', description: 'Vite config file' },
84+
{ value: 'vite.config.js', description: 'Vite config file' },
85+
],
86+
},
87+
mode: {
88+
handler: () => [
89+
{ value: 'development', description: 'Development mode' },
90+
{ value: 'production', description: 'Production mode' },
91+
],
92+
},
93+
logLevel: {
94+
handler: () => [
95+
{ value: 'info', description: 'Info level' },
96+
{ value: 'warn', description: 'Warn level' },
97+
{ value: 'error', description: 'Error level' },
98+
{ value: 'silent', description: 'Silent level' },
99+
],
100+
},
101+
},
102+
103+
subCommands: {
104+
lint: {
105+
handler: () => [
106+
{ value: 'main.ts', description: 'Main file' },
107+
{ value: 'index.ts', description: 'Index file' },
108+
],
109+
},
110+
dev: {
111+
options: {
112+
port: {
113+
handler: () => [
114+
{ value: '3000', description: 'Development server port' },
115+
{ value: '8080', description: 'Alternative port' },
116+
],
117+
},
118+
host: {
119+
handler: () => [
120+
{ value: 'localhost', description: 'Localhost' },
121+
{ value: '0.0.0.0', description: 'All interfaces' },
122+
],
123+
},
124+
},
125+
},
126+
},
127+
});
128+
129+
void completion;
130+
131+
const cli = createMain(main);
132+
cli();

0 commit comments

Comments
 (0)