|
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 | + */ |
5 | 5 |
|
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'; |
41 | 11 |
|
42 | 12 | export async function setupCompletionForPackageManager( |
43 | 13 | packageManager: string, |
44 | 14 | 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; |
272 | 32 | } |
273 | 33 | } |
0 commit comments