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.
- package/.github/FUNDING.yml +3 -0
- package/.vscodeignore +11 -0
- package/CLAUDE.md +230 -0
- package/LICENSE +21 -0
- package/README.md +529 -0
- package/assets/icon.png +0 -0
- package/bun.lock +908 -0
- package/docs/PERFORMANCE_OPTIMIZATIONS.md +240 -0
- package/docs/PRESET_AND_DYNAMIC_LABELS.md +536 -0
- package/docs/SAMPLE-CONFIGURATIONS.md +973 -0
- package/eslint.config.mjs +41 -0
- package/package.json +605 -0
- package/src/config-cli.ts +1287 -0
- package/src/configuration.ts +530 -0
- package/src/dynamic-label.ts +360 -0
- package/src/executor.ts +406 -0
- package/src/extension.ts +1754 -0
- package/src/history.ts +175 -0
- package/src/material-icons.ts +388 -0
- package/src/notifications.ts +189 -0
- package/src/output-panel.ts +403 -0
- package/src/preset-manager.ts +406 -0
- package/src/theme.ts +318 -0
- package/src/types.ts +368 -0
- package/src/utils/debounce.ts +91 -0
- package/src/visibility.ts +283 -0
- package/tsconfig.dev.json +10 -0
- package/tsconfig.json +19 -0
package/src/executor.ts
ADDED
|
@@ -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
|
+
}
|