Skip to content

Commit cec5d29

Browse files
authored
fix: separate pkg manager completion handling from the core API (#31)
* important update * lint
1 parent 80e3d05 commit cec5d29

File tree

14 files changed

+236
-658
lines changed

14 files changed

+236
-658
lines changed

bin/cli.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
#!/usr/bin/env node
22

33
import cac from 'cac';
4-
import { script, Completion } from '../src/index.js';
4+
import { script } from '../src/t.js';
55
import tab from '../src/cac.js';
66

77
import { setupCompletionForPackageManager } from './completion-handlers';
8+
import { PackageManagerCompletion } from './package-manager-completion.js';
89

910
const packageManagers = ['npm', 'pnpm', 'yarn', 'bun'];
1011
const shells = ['zsh', 'bash', 'fish', 'powershell'];
@@ -27,8 +28,8 @@ async function main() {
2728

2829
const dashIndex = process.argv.indexOf('--');
2930
if (dashIndex !== -1) {
30-
// TOOD: there's no Completion anymore
31-
const completion = new Completion();
31+
// Use the new PackageManagerCompletion wrapper
32+
const completion = new PackageManagerCompletion(packageManager);
3233
setupCompletionForPackageManager(packageManager, completion);
3334
const toComplete = process.argv.slice(dashIndex + 1);
3435
await completion.parse(toComplete);

bin/completion-handlers.ts

Lines changed: 49 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
// TODO: i do not see any completion functionality in this file. nothing is being provided for the defined commands of these package managers. this is a blocker for release. every each of them should be handled.
2-
import { Completion } from '../src/index.js';
3-
4-
const noopCompletion = async () => [];
2+
import { PackageManagerCompletion } from './package-manager-completion.js';
53

64
export function setupCompletionForPackageManager(
75
packageManager: string,
8-
completion: Completion
6+
completion: PackageManagerCompletion
97
) {
108
if (packageManager === 'pnpm') {
119
setupPnpmCompletions(completion);
@@ -17,54 +15,59 @@ export function setupCompletionForPackageManager(
1715
setupBunCompletions(completion);
1816
}
1917

20-
// TODO: the core functionality of tab should have nothing related to package managers. even though completion is not there anymore, but this is something to consider.
21-
completion.setPackageManager(packageManager);
18+
// Note: Package manager logic is now handled by PackageManagerCompletion wrapper
2219
}
2320

24-
export function setupPnpmCompletions(completion: Completion) {
25-
completion.addCommand('add', 'Install a package', [], noopCompletion);
26-
completion.addCommand('remove', 'Remove a package', [], noopCompletion);
27-
completion.addCommand(
28-
'install',
29-
'Install all dependencies',
30-
[],
31-
noopCompletion
32-
);
33-
completion.addCommand('update', 'Update packages', [], noopCompletion);
34-
completion.addCommand('exec', 'Execute a command', [], noopCompletion);
35-
completion.addCommand('run', 'Run a script', [], noopCompletion);
36-
completion.addCommand('publish', 'Publish package', [], noopCompletion);
37-
completion.addCommand('test', 'Run tests', [], noopCompletion);
38-
completion.addCommand('build', 'Build project', [], noopCompletion);
21+
export function setupPnpmCompletions(completion: PackageManagerCompletion) {
22+
completion.command('add', 'Install a package');
23+
completion.command('remove', 'Remove a package');
24+
completion.command('install', 'Install dependencies');
25+
completion.command('update', 'Update dependencies');
26+
completion.command('run', 'Run a script');
27+
completion.command('exec', 'Execute a command');
28+
completion.command('dlx', 'Run a package without installing');
29+
completion.command('create', 'Create a new project');
30+
completion.command('init', 'Initialize a new project');
31+
completion.command('publish', 'Publish the package');
32+
completion.command('pack', 'Create a tarball');
33+
completion.command('link', 'Link a package');
34+
completion.command('unlink', 'Unlink a package');
35+
completion.command('outdated', 'Check for outdated packages');
36+
completion.command('audit', 'Run security audit');
37+
completion.command('list', 'List installed packages');
3938
}
4039

41-
export function setupNpmCompletions(completion: Completion) {
42-
completion.addCommand('install', 'Install a package', [], noopCompletion);
43-
completion.addCommand('uninstall', 'Uninstall a package', [], noopCompletion);
44-
completion.addCommand('run', 'Run a script', [], noopCompletion);
45-
completion.addCommand('test', 'Run tests', [], noopCompletion);
46-
completion.addCommand('publish', 'Publish package', [], noopCompletion);
47-
completion.addCommand('update', 'Update packages', [], noopCompletion);
48-
completion.addCommand('start', 'Start the application', [], noopCompletion);
49-
completion.addCommand('build', 'Build project', [], noopCompletion);
40+
export function setupNpmCompletions(completion: PackageManagerCompletion) {
41+
completion.command('install', 'Install a package');
42+
completion.command('uninstall', 'Remove a package');
43+
completion.command('update', 'Update dependencies');
44+
completion.command('run', 'Run a script');
45+
completion.command('exec', 'Execute a command');
46+
completion.command('init', 'Initialize a new project');
47+
completion.command('publish', 'Publish the package');
48+
completion.command('pack', 'Create a tarball');
49+
completion.command('link', 'Link a package');
50+
completion.command('unlink', 'Unlink a package');
5051
}
5152

52-
export function setupYarnCompletions(completion: Completion) {
53-
completion.addCommand('add', 'Add a package', [], noopCompletion);
54-
completion.addCommand('remove', 'Remove a package', [], noopCompletion);
55-
completion.addCommand('run', 'Run a script', [], noopCompletion);
56-
completion.addCommand('test', 'Run tests', [], noopCompletion);
57-
completion.addCommand('publish', 'Publish package', [], noopCompletion);
58-
completion.addCommand('install', 'Install dependencies', [], noopCompletion);
59-
completion.addCommand('build', 'Build project', [], noopCompletion);
53+
export function setupYarnCompletions(completion: PackageManagerCompletion) {
54+
completion.command('add', 'Install a package');
55+
completion.command('remove', 'Remove a package');
56+
completion.command('install', 'Install dependencies');
57+
completion.command('upgrade', 'Update dependencies');
58+
completion.command('run', 'Run a script');
59+
completion.command('exec', 'Execute a command');
60+
completion.command('create', 'Create a new project');
61+
completion.command('init', 'Initialize a new project');
6062
}
6163

62-
export function setupBunCompletions(completion: Completion) {
63-
completion.addCommand('add', 'Add a package', [], noopCompletion);
64-
completion.addCommand('remove', 'Remove a package', [], noopCompletion);
65-
completion.addCommand('run', 'Run a script', [], noopCompletion);
66-
completion.addCommand('test', 'Run tests', [], noopCompletion);
67-
completion.addCommand('install', 'Install dependencies', [], noopCompletion);
68-
completion.addCommand('update', 'Update packages', [], noopCompletion);
69-
completion.addCommand('build', 'Build project', [], noopCompletion);
64+
export function setupBunCompletions(completion: PackageManagerCompletion) {
65+
completion.command('add', 'Install a package');
66+
completion.command('remove', 'Remove a package');
67+
completion.command('install', 'Install dependencies');
68+
completion.command('update', 'Update dependencies');
69+
completion.command('run', 'Run a script');
70+
completion.command('x', 'Execute a command');
71+
completion.command('create', 'Create a new project');
72+
completion.command('init', 'Initialize a new project');
7073
}

bin/package-manager-completion.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { execSync } from 'child_process';
2+
import { RootCommand } from '../src/t.js';
3+
4+
function debugLog(...args: any[]) {
5+
if (process.env.DEBUG) {
6+
console.error('[DEBUG]', ...args);
7+
}
8+
}
9+
10+
async function checkCliHasCompletions(
11+
cliName: string,
12+
packageManager: string
13+
): Promise<boolean> {
14+
try {
15+
debugLog(`Checking if ${cliName} has completions via ${packageManager}`);
16+
const command = `${packageManager} ${cliName} complete --`;
17+
const result = execSync(command, {
18+
encoding: 'utf8',
19+
stdio: ['pipe', 'pipe', 'ignore'],
20+
timeout: 1000,
21+
});
22+
const hasCompletions = !!result.trim();
23+
debugLog(`${cliName} supports completions: ${hasCompletions}`);
24+
return hasCompletions;
25+
} catch (error) {
26+
debugLog(`Error checking completions for ${cliName}:`, error);
27+
return false;
28+
}
29+
}
30+
31+
async function getCliCompletions(
32+
cliName: string,
33+
packageManager: string,
34+
args: string[]
35+
): Promise<string[]> {
36+
try {
37+
const completeArgs = args.map((arg) =>
38+
arg.includes(' ') ? `"${arg}"` : arg
39+
);
40+
const completeCommand = `${packageManager} ${cliName} complete -- ${completeArgs.join(' ')}`;
41+
debugLog(`Getting completions with command: ${completeCommand}`);
42+
43+
const result = execSync(completeCommand, {
44+
encoding: 'utf8',
45+
stdio: ['pipe', 'pipe', 'ignore'],
46+
timeout: 1000,
47+
});
48+
49+
const completions = result.trim().split('\n').filter(Boolean);
50+
debugLog(`Got ${completions.length} completions from ${cliName}`);
51+
return completions;
52+
} catch (error) {
53+
debugLog(`Error getting completions from ${cliName}:`, error);
54+
return [];
55+
}
56+
}
57+
58+
/**
59+
* Package Manager Completion Wrapper
60+
*
61+
* This extends RootCommand and adds package manager-specific logic.
62+
* It acts as a layer on top of the core tab library.
63+
*/
64+
export class PackageManagerCompletion extends RootCommand {
65+
private packageManager: string;
66+
67+
constructor(packageManager: string) {
68+
super();
69+
this.packageManager = packageManager;
70+
}
71+
72+
// Enhanced parse method with package manager logic
73+
async parse(args: string[]) {
74+
// Handle package manager completions first
75+
if (args.length >= 1 && args[0].trim() !== '') {
76+
const potentialCliName = args[0];
77+
const knownCommands = [...this.commands.keys()];
78+
79+
if (!knownCommands.includes(potentialCliName)) {
80+
const hasCompletions = await checkCliHasCompletions(
81+
potentialCliName,
82+
this.packageManager
83+
);
84+
85+
if (hasCompletions) {
86+
const cliArgs = args.slice(1);
87+
const suggestions = await getCliCompletions(
88+
potentialCliName,
89+
this.packageManager,
90+
cliArgs
91+
);
92+
93+
if (suggestions.length > 0) {
94+
// Print completions directly in the same format as the core library
95+
for (const suggestion of suggestions) {
96+
if (suggestion.startsWith(':')) continue;
97+
98+
if (suggestion.includes('\t')) {
99+
const [value, description] = suggestion.split('\t');
100+
console.log(`${value}\t${description}`);
101+
} else {
102+
console.log(suggestion);
103+
}
104+
}
105+
console.log(':4'); // Shell completion directive (NoFileComp)
106+
return;
107+
}
108+
}
109+
}
110+
}
111+
112+
// Fall back to regular completion logic (shows basic package manager commands)
113+
return super.parse(args);
114+
}
115+
}

examples/demo.commander.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,9 @@ const completion = tab(program);
6161

6262
// Configure custom completions
6363
for (const command of completion.commands.values()) {
64-
if (command.name === 'lint') {
65-
command.handler = () => {
66-
return [
67-
{ value: 'src/**/*.ts', description: 'TypeScript source files' },
68-
{ value: 'tests/**/*.ts', description: 'Test files' },
69-
];
70-
};
64+
if (command.value === 'lint') {
65+
// Note: Direct handler assignment is not supported in the current API
66+
// Custom completion logic would need to be implemented differently
7167
}
7268

7369
for (const [option, config] of command.options.entries()) {

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"name": "@bombsh/tab",
33
"version": "0.0.0",
4-
"main": "./dist/index.js",
5-
"types": "./dist/index.d.ts",
4+
"main": "./dist/t.js",
5+
"types": "./dist/t.d.ts",
66
"type": "module",
77
"bin": {
88
"tab": "./dist/bin/cli.js"
@@ -41,9 +41,9 @@
4141
},
4242
"exports": {
4343
".": {
44-
"types": "./dist/index.d.ts",
45-
"import": "./dist/index.js",
46-
"require": "./dist/index.cjs"
44+
"types": "./dist/t.d.ts",
45+
"import": "./dist/t.js",
46+
"require": "./dist/t.cjs"
4747
},
4848
"./citty": {
4949
"types": "./dist/citty.d.ts",

src/bash.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ShellCompDirective } from './';
1+
import { ShellCompDirective } from './t';
22

33
export function generate(name: string, exec: string): string {
44
// Replace '-' and ':' with '_' for variable names

0 commit comments

Comments
 (0)