statusbar-quick-actions 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,406 @@
1
+ /**
2
+ * Command executor for StatusBar Quick Actions
3
+ */
4
+
5
+ import { ExecutionResult, ExecutionOptions, ButtonCommand } from "./types";
6
+ import * as vscode from "vscode";
7
+ import { exec, spawn } from "child_process";
8
+ import { promisify } from "util";
9
+ import * as path from "path";
10
+ import * as fs from "fs";
11
+
12
+ const execAsync = promisify(exec);
13
+
14
+ export class CommandExecutor {
15
+ /**
16
+ * Execute a command
17
+ */
18
+ public async execute(
19
+ command: ButtonCommand,
20
+ options: ExecutionOptions,
21
+ ): Promise<ExecutionResult> {
22
+ // If streaming is enabled, use executeWithStreaming
23
+ if (options.streaming?.enabled) {
24
+ return this.executeWithStreaming(command, options);
25
+ }
26
+
27
+ // Otherwise, use existing execAsync logic for backward compatibility
28
+ const startTime = Date.now();
29
+ const timeout = options.timeout || 30000;
30
+
31
+ try {
32
+ let cmd: string;
33
+ let args: string[] = [];
34
+ let fullCommand: string;
35
+
36
+ // Process different command types
37
+ switch (command.type) {
38
+ case "npm":
39
+ cmd = "npm";
40
+ args = ["run", command.script || ""];
41
+ fullCommand = `${cmd} ${args.join(" ")}`;
42
+ break;
43
+ case "yarn":
44
+ cmd = "yarn";
45
+ args = [command.script || ""];
46
+ fullCommand = `${cmd} ${args.join(" ")}`;
47
+ break;
48
+ case "pnpm":
49
+ cmd = "pnpm";
50
+ args = ["run", command.script || ""];
51
+ fullCommand = `${cmd} ${args.join(" ")}`;
52
+ break;
53
+ case "bun":
54
+ cmd = "bun";
55
+ args = ["run", command.script || ""];
56
+ fullCommand = `${cmd} ${args.join(" ")}`;
57
+ break;
58
+ case "bunx":
59
+ cmd = "bunx";
60
+ args = [command.script || ""].concat(command.args || []);
61
+ fullCommand = `${cmd} ${args.join(" ")}`;
62
+ break;
63
+ case "npx":
64
+ cmd = "npx";
65
+ args = [command.script || ""].concat(command.args || []);
66
+ fullCommand = `${cmd} ${args.join(" ")}`;
67
+ break;
68
+ case "pnpx":
69
+ cmd = "pnpx";
70
+ args = [command.script || ""].concat(command.args || []);
71
+ fullCommand = `${cmd} ${args.join(" ")}`;
72
+ break;
73
+ case "github":
74
+ cmd = "gh";
75
+ args = [command.command || ""].concat(command.args || []);
76
+ fullCommand = `${cmd} ${args.join(" ")}`;
77
+ break;
78
+ case "vscode":
79
+ // Execute VSCode command
80
+ await vscode.commands.executeCommand(
81
+ command.command || "",
82
+ ...(command.args || []),
83
+ );
84
+ return {
85
+ code: 0,
86
+ stdout: "VSCode command executed successfully",
87
+ stderr: "",
88
+ duration: Date.now() - startTime,
89
+ timestamp: new Date(),
90
+ command: command.command || "",
91
+ };
92
+ case "task": {
93
+ // Execute VSCode task
94
+ const tasks = await vscode.tasks.fetchTasks();
95
+ const task = tasks.find((t) => t.name === command.command);
96
+ if (task) {
97
+ await vscode.tasks.executeTask(task);
98
+ return {
99
+ code: 0,
100
+ stdout: `Task '${command.command}' started successfully`,
101
+ stderr: "",
102
+ duration: Date.now() - startTime,
103
+ timestamp: new Date(),
104
+ command: command.command || "",
105
+ };
106
+ } else {
107
+ throw new Error(`Task '${command.command}' not found`);
108
+ }
109
+ }
110
+ case "detect": {
111
+ // Auto-detect package manager and run script
112
+ const workspacePath =
113
+ options.workingDirectory ||
114
+ vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ||
115
+ "";
116
+ const detectedPm = await this.detectPackageManager(workspacePath);
117
+ if (!detectedPm) {
118
+ throw new Error("Could not detect package manager");
119
+ }
120
+ cmd = detectedPm;
121
+ args =
122
+ detectedPm === "yarn"
123
+ ? [command.script || ""]
124
+ : ["run", command.script || ""];
125
+ fullCommand = `${cmd} ${args.join(" ")}`;
126
+ break;
127
+ }
128
+ case "shell":
129
+ default:
130
+ cmd = command.command || "";
131
+ args = command.args || [];
132
+ fullCommand = args.length > 0 ? `${cmd} ${args.join(" ")}` : cmd;
133
+ break;
134
+ }
135
+
136
+ // Execute using child_process for proper output capture
137
+ const cwd =
138
+ options.workingDirectory ||
139
+ vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ||
140
+ process.cwd();
141
+ const env = { ...process.env, ...options.environment };
142
+
143
+ try {
144
+ const { stdout, stderr } = await execAsync(fullCommand, {
145
+ cwd,
146
+ env,
147
+ timeout,
148
+ maxBuffer: 10 * 1024 * 1024, // 10MB buffer
149
+ windowsHide: true,
150
+ });
151
+
152
+ return {
153
+ code: 0,
154
+ stdout: stdout.toString().trim(),
155
+ stderr: stderr.toString().trim(),
156
+ duration: Date.now() - startTime,
157
+ timestamp: new Date(),
158
+ command: fullCommand,
159
+ };
160
+ } catch (execError: unknown) {
161
+ // exec throws on non-zero exit code
162
+ const error = execError as {
163
+ code?: number;
164
+ stdout?: string;
165
+ stderr?: string;
166
+ message?: string;
167
+ };
168
+ return {
169
+ code: error.code || -1,
170
+ stdout: error.stdout?.toString().trim() || "",
171
+ stderr: error.stderr?.toString().trim() || error.message || "",
172
+ duration: Date.now() - startTime,
173
+ timestamp: new Date(),
174
+ command: fullCommand,
175
+ };
176
+ }
177
+ } catch (error) {
178
+ return {
179
+ code: -1,
180
+ stdout: "",
181
+ stderr: error instanceof Error ? error.message : String(error),
182
+ duration: Date.now() - startTime,
183
+ timestamp: new Date(),
184
+ command: command.command || "",
185
+ };
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Build command string and arguments from ButtonCommand
191
+ */
192
+ private buildCommand(command: ButtonCommand): {
193
+ cmd: string;
194
+ args: string[];
195
+ fullCommand: string;
196
+ } {
197
+ let cmd: string;
198
+ let args: string[] = [];
199
+ let fullCommand: string;
200
+
201
+ switch (command.type) {
202
+ case "npm":
203
+ cmd = "npm";
204
+ args = ["run", command.script || ""];
205
+ fullCommand = `${cmd} ${args.join(" ")}`;
206
+ break;
207
+ case "yarn":
208
+ cmd = "yarn";
209
+ args = [command.script || ""];
210
+ fullCommand = `${cmd} ${args.join(" ")}`;
211
+ break;
212
+ case "pnpm":
213
+ cmd = "pnpm";
214
+ args = ["run", command.script || ""];
215
+ fullCommand = `${cmd} ${args.join(" ")}`;
216
+ break;
217
+ case "bun":
218
+ cmd = "bun";
219
+ args = ["run", command.script || ""];
220
+ fullCommand = `${cmd} ${args.join(" ")}`;
221
+ break;
222
+ case "bunx":
223
+ cmd = "bunx";
224
+ args = [command.script || ""].concat(command.args || []);
225
+ fullCommand = `${cmd} ${args.join(" ")}`;
226
+ break;
227
+ case "npx":
228
+ cmd = "npx";
229
+ args = [command.script || ""].concat(command.args || []);
230
+ fullCommand = `${cmd} ${args.join(" ")}`;
231
+ break;
232
+ case "pnpx":
233
+ cmd = "pnpx";
234
+ args = [command.script || ""].concat(command.args || []);
235
+ fullCommand = `${cmd} ${args.join(" ")}`;
236
+ break;
237
+ case "github":
238
+ cmd = "gh";
239
+ args = [command.command || ""].concat(command.args || []);
240
+ fullCommand = `${cmd} ${args.join(" ")}`;
241
+ break;
242
+ case "shell":
243
+ default:
244
+ cmd = command.command || "";
245
+ args = command.args || [];
246
+ fullCommand = args.length > 0 ? `${cmd} ${args.join(" ")}` : cmd;
247
+ break;
248
+ }
249
+
250
+ return { cmd, args, fullCommand };
251
+ }
252
+
253
+ /**
254
+ * Execute a command with streaming output support
255
+ */
256
+ public async executeWithStreaming(
257
+ command: ButtonCommand,
258
+ options: ExecutionOptions,
259
+ ): Promise<ExecutionResult> {
260
+ const startTime = Date.now();
261
+
262
+ // Build command
263
+ const { cmd, args, fullCommand } = this.buildCommand(command);
264
+
265
+ const cwd =
266
+ options.workingDirectory ||
267
+ vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ||
268
+ process.cwd();
269
+ const env = { ...process.env, ...options.environment };
270
+
271
+ return new Promise((resolve, reject) => {
272
+ const child = spawn(cmd, args, {
273
+ cwd,
274
+ env,
275
+ shell: true,
276
+ windowsHide: true,
277
+ });
278
+
279
+ let stdout = "";
280
+ let stderr = "";
281
+
282
+ // Handle stdout data
283
+ child.stdout?.on("data", (data) => {
284
+ const text = data.toString();
285
+ stdout += text;
286
+ if (options.streaming?.onStdout) {
287
+ options.streaming.onStdout(text);
288
+ }
289
+ });
290
+
291
+ // Handle stderr data
292
+ child.stderr?.on("data", (data) => {
293
+ const text = data.toString();
294
+ stderr += text;
295
+ if (options.streaming?.onStderr) {
296
+ options.streaming.onStderr(text);
297
+ }
298
+ });
299
+
300
+ // Handle process exit
301
+ child.on("close", (code) => {
302
+ resolve({
303
+ code: code || 0,
304
+ stdout: stdout.trim(),
305
+ stderr: stderr.trim(),
306
+ duration: Date.now() - startTime,
307
+ timestamp: new Date(),
308
+ command: fullCommand,
309
+ });
310
+ });
311
+
312
+ // Handle errors
313
+ child.on("error", (error) => {
314
+ reject(error);
315
+ });
316
+
317
+ // Timeout handling
318
+ if (options.timeout) {
319
+ setTimeout(() => {
320
+ child.kill();
321
+ reject(
322
+ new Error(`Command execution timeout after ${options.timeout}ms`),
323
+ );
324
+ }, options.timeout);
325
+ }
326
+ });
327
+ }
328
+
329
+ /**
330
+ * Check if a command type is available
331
+ */
332
+ public async isCommandAvailable(commandType: string): Promise<boolean> {
333
+ try {
334
+ const checkCmd = process.platform === "win32" ? "where" : "which";
335
+ await execAsync(`${checkCmd} ${commandType}`, {
336
+ windowsHide: true,
337
+ });
338
+ return true;
339
+ } catch {
340
+ return false;
341
+ }
342
+ }
343
+
344
+ /**
345
+ * Get available package managers
346
+ */
347
+ public async getAvailablePackageManagers(): Promise<string[]> {
348
+ const managers: string[] = [];
349
+ const commands = ["npm", "yarn", "pnpm", "bun"];
350
+
351
+ for (const manager of commands) {
352
+ if (await this.isCommandAvailable(manager)) {
353
+ managers.push(manager);
354
+ }
355
+ }
356
+
357
+ return managers;
358
+ }
359
+
360
+ /**
361
+ * Detect package manager from workspace
362
+ */
363
+ public async detectPackageManager(
364
+ workspacePath: string,
365
+ ): Promise<string | null> {
366
+ if (!workspacePath) {
367
+ workspacePath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || "";
368
+ }
369
+
370
+ if (!workspacePath) {
371
+ return null;
372
+ }
373
+
374
+ try {
375
+ // Check for lock files in priority order
376
+ const lockFiles = [
377
+ { file: "bun.lockb", manager: "bun" },
378
+ { file: "pnpm-lock.yaml", manager: "pnpm" },
379
+ { file: "yarn.lock", manager: "yarn" },
380
+ { file: "package-lock.json", manager: "npm" },
381
+ ];
382
+
383
+ for (const { file, manager } of lockFiles) {
384
+ const lockPath = path.join(workspacePath, file);
385
+ if (fs.existsSync(lockPath)) {
386
+ // Verify the package manager is actually available
387
+ if (await this.isCommandAvailable(manager)) {
388
+ return manager;
389
+ }
390
+ }
391
+ }
392
+
393
+ // Fallback: check if package.json exists and return first available manager
394
+ const packageJsonPath = path.join(workspacePath, "package.json");
395
+ if (fs.existsSync(packageJsonPath)) {
396
+ const availableManagers = await this.getAvailablePackageManagers();
397
+ return availableManagers[0] || null;
398
+ }
399
+
400
+ return null;
401
+ } catch (error) {
402
+ console.error("Error detecting package manager:", error);
403
+ return null;
404
+ }
405
+ }
406
+ }