Skip to content

Commit 9a4d53f

Browse files
committed
big update
1 parent 3291105 commit 9a4d53f

File tree

9 files changed

+3863
-266
lines changed

9 files changed

+3863
-266
lines changed

bin/completion-handlers.ts

Lines changed: 26 additions & 266 deletions
Original file line numberDiff line numberDiff line change
@@ -1,273 +1,33 @@
1-
import { PackageManagerCompletion } from './package-manager-completion.js';
2-
import { readFileSync } from 'fs';
3-
import { execSync } from 'child_process';
4-
import type { Complete } from '../src/t.js';
1+
/**
2+
* Main entry point for package manager completion handlers
3+
* Delegates to specific package manager handlers
4+
*/
55

6-
// Helper functions for dynamic completions
7-
function getPackageJsonScripts(): string[] {
8-
try {
9-
const packageJson = JSON.parse(readFileSync('package.json', 'utf8'));
10-
return Object.keys(packageJson.scripts || {});
11-
} catch {
12-
return [];
13-
}
14-
}
15-
16-
function getPackageJsonDependencies(): string[] {
17-
try {
18-
const packageJson = JSON.parse(readFileSync('package.json', 'utf8'));
19-
const deps = {
20-
...packageJson.dependencies,
21-
...packageJson.devDependencies,
22-
...packageJson.peerDependencies,
23-
...packageJson.optionalDependencies,
24-
};
25-
return Object.keys(deps);
26-
} catch {
27-
return [];
28-
}
29-
}
30-
31-
// Common completion handlers
32-
const scriptCompletion = async (complete: Complete) => {
33-
const scripts = getPackageJsonScripts();
34-
scripts.forEach((script) => complete(script, `Run ${script} script`));
35-
};
36-
37-
const dependencyCompletion = async (complete: Complete) => {
38-
const deps = getPackageJsonDependencies();
39-
deps.forEach((dep) => complete(dep, ''));
40-
};
6+
import type { PackageManagerCompletion } from './package-manager-completion.js';
7+
import { setupPnpmCompletions } from './handlers/pnpm-handler.js';
8+
import { setupNpmCompletions } from './handlers/npm-handler.js';
9+
import { setupYarnCompletions } from './handlers/yarn-handler.js';
10+
import { setupBunCompletions } from './handlers/bun-handler.js';
4111

4212
export async function setupCompletionForPackageManager(
4313
packageManager: string,
4414
completion: PackageManagerCompletion
45-
) {
46-
if (packageManager === 'pnpm') {
47-
await setupPnpmCompletions(completion);
48-
} else if (packageManager === 'npm') {
49-
await setupNpmCompletions(completion);
50-
} else if (packageManager === 'yarn') {
51-
await setupYarnCompletions(completion);
52-
} else if (packageManager === 'bun') {
53-
await setupBunCompletions(completion);
54-
}
55-
}
56-
57-
export async function setupPnpmCompletions(
58-
completion: PackageManagerCompletion
59-
) {
60-
try {
61-
const commandsWithDescriptions = await getPnpmCommandsFromMainHelp();
62-
63-
for (const [command, description] of Object.entries(
64-
commandsWithDescriptions
65-
)) {
66-
const cmd = completion.command(command, description);
67-
68-
if (['remove', 'rm', 'update', 'up'].includes(command)) {
69-
cmd.argument('package', dependencyCompletion);
70-
}
71-
if (command === 'run') {
72-
cmd.argument('script', scriptCompletion, true);
73-
}
74-
75-
// TODO: AMIR: MANUAL OPTIONS ?
76-
77-
setupLazyOptionLoading(cmd, command);
78-
}
79-
} catch (error) {
80-
if (error instanceof Error) {
81-
console.error('Failed to setup pnpm completions:', error.message);
82-
} else {
83-
console.error('Failed to setup pnpm completions:', error);
84-
}
85-
}
86-
}
87-
88-
async function getPnpmCommandsFromMainHelp(): Promise<Record<string, string>> {
89-
try {
90-
const output = execSync('pnpm --help', { encoding: 'utf8', timeout: 3000 });
91-
const lines = output.split('\n');
92-
const commands: Record<string, string> = {};
93-
94-
let inCommandSection = false;
95-
let currentCommand = '';
96-
let currentDescription = '';
97-
98-
for (let i = 0; i < lines.length; i++) {
99-
const line = lines[i];
100-
101-
if (
102-
line.match(
103-
/^(Manage your dependencies|Review your dependencies|Run your scripts|Other|Manage your store):/
104-
)
105-
) {
106-
inCommandSection = true;
107-
continue;
108-
}
109-
110-
// exclude options section
111-
if (line.match(/^Options:/)) {
112-
break;
113-
}
114-
115-
if (inCommandSection && line.trim()) {
116-
const commandMatch = line.match(/^\s+([a-z,\s-]+?)\s{8,}(.+)$/);
117-
if (commandMatch) {
118-
if (currentCommand && currentDescription) {
119-
const commandNames = currentCommand
120-
.split(',')
121-
.map((c) => c.trim())
122-
.filter((c) => c);
123-
for (const cmd of commandNames) {
124-
commands[cmd] = currentDescription.trim();
125-
}
126-
}
127-
128-
const [, cmdPart, description] = commandMatch;
129-
currentCommand = cmdPart;
130-
currentDescription = description;
131-
132-
// sometimes the description is on multiple lines
133-
let j = i + 1;
134-
while (j < lines.length && lines[j].match(/^\s{25,}/)) {
135-
currentDescription += ' ' + lines[j].trim();
136-
j++;
137-
}
138-
i = j - 1;
139-
}
140-
}
141-
}
142-
143-
if (currentCommand && currentDescription) {
144-
const commandNames = currentCommand
145-
.split(',')
146-
.map((c) => c.trim())
147-
.filter((c) => c);
148-
for (const cmd of commandNames) {
149-
commands[cmd] = currentDescription.trim();
150-
}
151-
}
152-
153-
return commands;
154-
} catch (error) {
155-
if (error instanceof Error) {
156-
console.error('Failed to setup pnpm completions:', error.message);
157-
} else {
158-
console.error('Failed to setup pnpm completions:', error);
159-
}
160-
return {};
161-
}
162-
}
163-
164-
export async function setupNpmCompletions(
165-
completion: PackageManagerCompletion
166-
) {}
167-
168-
export async function setupYarnCompletions(
169-
completion: PackageManagerCompletion
170-
) {}
171-
172-
export async function setupBunCompletions(
173-
completion: PackageManagerCompletion
174-
) {}
175-
176-
function setupLazyOptionLoading(cmd: any, command: string) {
177-
cmd._lazyCommand = command;
178-
cmd._optionsLoaded = false;
179-
180-
const originalOptions = cmd.options;
181-
Object.defineProperty(cmd, 'options', {
182-
get() {
183-
if (!this._optionsLoaded) {
184-
this._optionsLoaded = true;
185-
loadDynamicOptionsSync(this, this._lazyCommand);
186-
}
187-
return originalOptions;
188-
},
189-
configurable: true,
190-
});
191-
}
192-
193-
function loadDynamicOptionsSync(cmd: any, command: string) {
194-
try {
195-
const output = execSync(`pnpm ${command} --help`, {
196-
encoding: 'utf8',
197-
timeout: 3000,
198-
});
199-
const lines = output.split('\n');
200-
let inOptionsSection = false;
201-
let currentOption = '';
202-
let currentDescription = '';
203-
let currentShortFlag: string | undefined;
204-
205-
for (let i = 0; i < lines.length; i++) {
206-
const line = lines[i];
207-
208-
if (line.match(/^Options:/)) {
209-
inOptionsSection = true;
210-
continue;
211-
}
212-
213-
if (inOptionsSection && line.match(/^[A-Z][a-z].*:/)) {
214-
break;
215-
}
216-
217-
if (inOptionsSection && line.trim()) {
218-
const optionMatch = line.match(
219-
/^\s*(?:-([A-Za-z]),\s*)?--([a-z-]+)(?!\s+<|\s+\[)\s+(.*)/
220-
);
221-
if (optionMatch) {
222-
if (currentOption && currentDescription) {
223-
const existingOption = cmd.options.get(currentOption);
224-
if (!existingOption) {
225-
cmd.option(
226-
currentOption,
227-
currentDescription.trim(),
228-
currentShortFlag
229-
);
230-
}
231-
}
232-
233-
const [, shortFlag, optionName, description] = optionMatch;
234-
235-
// TODO: AMIR: lets only proccess options that don't have <value> ?
236-
if (!line.includes('<') && !line.includes('[')) {
237-
currentOption = optionName;
238-
currentShortFlag = shortFlag || undefined;
239-
currentDescription = description;
240-
241-
let j = i + 1;
242-
while (
243-
j < lines.length &&
244-
lines[j].match(/^\s{25,}/) &&
245-
!lines[j].match(/^\s*(?:-[A-Za-z],\s*)?--[a-z-]/)
246-
) {
247-
currentDescription += ' ' + lines[j].trim();
248-
j++;
249-
}
250-
i = j - 1;
251-
} else {
252-
currentOption = '';
253-
currentDescription = '';
254-
currentShortFlag = undefined;
255-
}
256-
}
257-
}
258-
}
259-
260-
if (currentOption && currentDescription) {
261-
const existingOption = cmd.options.get(currentOption);
262-
if (!existingOption) {
263-
cmd.option(currentOption, currentDescription.trim(), currentShortFlag);
264-
}
265-
}
266-
} catch (error) {
267-
if (error instanceof Error) {
268-
console.error(`Failed to load options for ${command}:`, error.message);
269-
} else {
270-
console.error(`Failed to load options for ${command}:`, error);
271-
}
15+
): Promise<void> {
16+
switch (packageManager) {
17+
case 'pnpm':
18+
await setupPnpmCompletions(completion);
19+
break;
20+
case 'npm':
21+
await setupNpmCompletions(completion);
22+
break;
23+
case 'yarn':
24+
await setupYarnCompletions(completion);
25+
break;
26+
case 'bun':
27+
await setupBunCompletions(completion);
28+
break;
29+
default:
30+
// silently ignore unknown package managers
31+
break;
27232
}
27333
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { Complete } from '../../src/t.js';
2+
import {
3+
getPackageJsonScripts,
4+
getPackageJsonDependencies,
5+
} from '../utils/package-json-utils.js';
6+
7+
// provides completions for npm scripts from package.json.. like: start,dev,build
8+
export const packageJsonScriptCompletion = async (
9+
complete: Complete
10+
): Promise<void> => {
11+
getPackageJsonScripts().forEach((script) =>
12+
complete(script, `Run ${script} script`)
13+
);
14+
};
15+
16+
// provides completions for package dependencies from package.json.. `pnpm add <dependency>`
17+
export const packageJsonDependencyCompletion = async (
18+
complete: Complete
19+
): Promise<void> => {
20+
getPackageJsonDependencies().forEach((dep) => complete(dep, ''));
21+
};

bin/handlers/bun-handler.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import type { PackageManagerCompletion } from '../package-manager-completion.js';
2+
3+
export async function setupBunCompletions(
4+
completion: PackageManagerCompletion
5+
): Promise<void> {}

bin/handlers/npm-handler.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import type { PackageManagerCompletion } from '../package-manager-completion.js';
2+
3+
export async function setupNpmCompletions(
4+
completion: PackageManagerCompletion
5+
): Promise<void> {}

0 commit comments

Comments
 (0)