Skip to content

Commit 8defa6f

Browse files
authored
fix: package manager completion support (#78)
* fix: npm exec * prettier * changeset
1 parent 7178288 commit 8defa6f

File tree

2 files changed

+59
-32
lines changed

2 files changed

+59
-32
lines changed

.changeset/cuddly-clubs-count.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@bomb.sh/tab': patch
3+
---
4+
5+
npm exec support

bin/package-manager-completion.ts

Lines changed: 54 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,22 @@ async function checkCliHasCompletions(
1212
packageManager: string
1313
): Promise<boolean> {
1414
try {
15-
debugLog(`Checking if ${cliName} has completions via ${packageManager}`);
16-
const command = `${packageManager} ${cliName} complete --`;
17-
const result = execSync(command, {
15+
const result = execSync(`${cliName} complete --`, {
1816
encoding: 'utf8',
1917
stdio: ['pipe', 'pipe', 'ignore'],
2018
timeout: 1000,
2119
});
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);
20+
if (result.trim()) return true;
21+
} catch {}
22+
23+
try {
24+
const result = execSync(`${packageManager} ${cliName} complete --`, {
25+
encoding: 'utf8',
26+
stdio: ['pipe', 'pipe', 'ignore'],
27+
timeout: 1000,
28+
});
29+
return !!result.trim();
30+
} catch {
2731
return false;
2832
}
2933
}
@@ -33,24 +37,35 @@ async function getCliCompletions(
3337
packageManager: string,
3438
args: string[]
3539
): Promise<string[]> {
40+
const completeArgs = args.map((arg) =>
41+
arg.includes(' ') ? `"${arg}"` : arg
42+
);
43+
3644
try {
37-
const completeArgs = args.map((arg) =>
38-
arg.includes(' ') ? `"${arg}"` : arg
45+
const result = execSync(
46+
`${cliName} complete -- ${completeArgs.join(' ')}`,
47+
{
48+
encoding: 'utf8',
49+
stdio: ['pipe', 'pipe', 'ignore'],
50+
timeout: 1000,
51+
}
3952
);
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-
});
53+
if (result.trim()) {
54+
return result.trim().split('\n').filter(Boolean);
55+
}
56+
} catch {}
4857

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);
58+
try {
59+
const result = execSync(
60+
`${packageManager} ${cliName} complete -- ${completeArgs.join(' ')}`,
61+
{
62+
encoding: 'utf8',
63+
stdio: ['pipe', 'pipe', 'ignore'],
64+
timeout: 1000,
65+
}
66+
);
67+
return result.trim().split('\n').filter(Boolean);
68+
} catch {
5469
return [];
5570
}
5671
}
@@ -69,11 +84,18 @@ export class PackageManagerCompletion extends RootCommand {
6984
this.packageManager = packageManager;
7085
}
7186

72-
// Enhanced parse method with package manager logic
87+
private stripPackageManagerCommands(args: string[]): string[] {
88+
if (args.length === 0) return args;
89+
const execCommands = ['exec', 'x', 'run', 'dlx'];
90+
if (execCommands.includes(args[0])) return args.slice(1);
91+
return args;
92+
}
93+
7394
async parse(args: string[]) {
74-
// Handle package manager completions first
75-
if (args.length >= 1 && args[0].trim() !== '') {
76-
const potentialCliName = args[0];
95+
const normalizedArgs = this.stripPackageManagerCommands(args);
96+
97+
if (normalizedArgs.length >= 1 && normalizedArgs[0].trim() !== '') {
98+
const potentialCliName = normalizedArgs[0];
7799
const knownCommands = [...this.commands.keys()];
78100

79101
if (!knownCommands.includes(potentialCliName)) {
@@ -83,33 +105,33 @@ export class PackageManagerCompletion extends RootCommand {
83105
);
84106

85107
if (hasCompletions) {
86-
const cliArgs = args.slice(1);
108+
const cliArgs = normalizedArgs.slice(1);
87109
const suggestions = await getCliCompletions(
88110
potentialCliName,
89111
this.packageManager,
90112
cliArgs
91113
);
92114

93115
if (suggestions.length > 0) {
94-
// Print completions directly in the same format as the core library
116+
debugLog(
117+
`Returning ${suggestions.length} completions for ${potentialCliName}`
118+
);
95119
for (const suggestion of suggestions) {
96120
if (suggestion.startsWith(':')) continue;
97-
98121
if (suggestion.includes('\t')) {
99122
const [value, description] = suggestion.split('\t');
100123
console.log(`${value}\t${description}`);
101124
} else {
102125
console.log(suggestion);
103126
}
104127
}
105-
console.log(':4'); // Shell completion directive (NoFileComp)
128+
console.log(':4');
106129
return;
107130
}
108131
}
109132
}
110133
}
111134

112-
// Fall back to regular completion logic (shows basic package manager commands)
113135
return super.parse(args);
114136
}
115137
}

0 commit comments

Comments
 (0)