buddy-workbench 0.1.53 → 0.1.55
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/bin/devbuddy.js +35 -10
- package/package.json +1 -1
- package/server/lib/free-port.js +2 -2
- package/server/services/bookmark-sync.js +7 -3
- package/server/services/browser.js +6 -6
- package/server/services/clipboard-history.js +1 -1
- package/server/services/dev-configurations.js +16 -4
- package/server/services/dialog.js +2 -2
- package/server/services/process-manager.js +5 -2
- package/server/services/script-supervisor.js +6 -1
- package/ui/dist/assets/{index-PqggIpES.js → index-DfwdSt_v.js} +2 -2
- package/ui/dist/index.html +1 -1
- package/ui/dist/sw.js +2 -2
package/bin/devbuddy.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { execFileSync,
|
|
3
|
+
import { execFileSync, spawn } from 'node:child_process';
|
|
4
4
|
import { existsSync, mkdirSync, openSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
5
5
|
import { homedir } from 'node:os';
|
|
6
6
|
import { dirname, join } from 'node:path';
|
|
@@ -46,10 +46,24 @@ function packageDetails() {
|
|
|
46
46
|
return JSON.parse(readFileSync(packageJsonFile, 'utf8'));
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
function version() {
|
|
50
|
+
console.log(packageDetails().version);
|
|
51
|
+
}
|
|
52
|
+
|
|
49
53
|
function npmCommand() {
|
|
50
54
|
return process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
51
55
|
}
|
|
52
56
|
|
|
57
|
+
function npmExecOptions(options = {}) {
|
|
58
|
+
return {
|
|
59
|
+
...options,
|
|
60
|
+
// npm is a .cmd shim on Windows and must be launched through cmd.exe.
|
|
61
|
+
// Keep that helper shell hidden so self-update does not flash a window.
|
|
62
|
+
shell: process.platform === 'win32',
|
|
63
|
+
windowsHide: process.platform === 'win32'
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
53
67
|
function parseUpdateVersion(args) {
|
|
54
68
|
if (args.length > 1) throw new Error('Usage: devbuddy self-update [version]');
|
|
55
69
|
if (!args[0]) return 'latest';
|
|
@@ -61,13 +75,14 @@ function parseUpdateVersion(args) {
|
|
|
61
75
|
|
|
62
76
|
function latestPublishedVersion(packageName) {
|
|
63
77
|
try {
|
|
64
|
-
return execFileSync(npmCommand(), ['view', packageName, 'version'], {
|
|
78
|
+
return execFileSync(npmCommand(), ['view', packageName, 'version'], npmExecOptions({
|
|
65
79
|
cwd: root,
|
|
66
80
|
encoding: 'utf8',
|
|
67
81
|
stdio: ['ignore', 'pipe', 'pipe']
|
|
68
|
-
}).trim();
|
|
69
|
-
} catch {
|
|
70
|
-
|
|
82
|
+
})).trim();
|
|
83
|
+
} catch (error) {
|
|
84
|
+
const details = String(error?.stderr || '').trim();
|
|
85
|
+
throw new Error(`Unable to query the latest ${packageName} version from npm.${details ? ` ${details}` : ''}`);
|
|
71
86
|
}
|
|
72
87
|
}
|
|
73
88
|
|
|
@@ -84,12 +99,13 @@ function selfUpdate(args) {
|
|
|
84
99
|
|
|
85
100
|
console.log(`Updating ${details.name} from ${currentVersion} to ${version}…`);
|
|
86
101
|
try {
|
|
87
|
-
execFileSync(npmCommand(), ['install', '--global', `${details.name}@${version}`], {
|
|
102
|
+
execFileSync(npmCommand(), ['install', '--global', `${details.name}@${version}`], npmExecOptions({
|
|
88
103
|
cwd: root,
|
|
89
104
|
stdio: 'inherit'
|
|
90
|
-
});
|
|
91
|
-
} catch {
|
|
92
|
-
|
|
105
|
+
}));
|
|
106
|
+
} catch (error) {
|
|
107
|
+
const detailsText = String(error?.stderr || '').trim();
|
|
108
|
+
throw new Error(`Unable to install ${details.name}@${version}. ${detailsText || 'Check npm permissions and network access.'}`);
|
|
93
109
|
}
|
|
94
110
|
console.log(`Updated ${details.name} to ${targetVersion}.`);
|
|
95
111
|
}
|
|
@@ -129,7 +145,10 @@ function sendSignal(pid, signal) {
|
|
|
129
145
|
return true;
|
|
130
146
|
} catch (err2) {
|
|
131
147
|
try {
|
|
132
|
-
|
|
148
|
+
execFileSync('kill', [`-${signal === 'SIGTERM' ? '15' : '9'}`, String(pid)], {
|
|
149
|
+
stdio: 'ignore',
|
|
150
|
+
windowsHide: process.platform === 'win32'
|
|
151
|
+
});
|
|
133
152
|
return true;
|
|
134
153
|
} catch {
|
|
135
154
|
return false;
|
|
@@ -240,6 +259,7 @@ Commands:
|
|
|
240
259
|
help Display this help message
|
|
241
260
|
|
|
242
261
|
Options:
|
|
262
|
+
-v, --version Display the installed DevBuddy version
|
|
243
263
|
--port <number> Port to listen on (default: 3100)
|
|
244
264
|
|
|
245
265
|
Examples:
|
|
@@ -252,6 +272,11 @@ async function main() {
|
|
|
252
272
|
const args = process.argv.slice(3);
|
|
253
273
|
|
|
254
274
|
switch (command.toLowerCase()) {
|
|
275
|
+
case '-v':
|
|
276
|
+
case '--version':
|
|
277
|
+
if (args.length) throw new Error('Usage: devbuddy --version');
|
|
278
|
+
version();
|
|
279
|
+
break;
|
|
255
280
|
case 'start':
|
|
256
281
|
await start(parsePort(args));
|
|
257
282
|
break;
|
package/package.json
CHANGED
package/server/lib/free-port.js
CHANGED
|
@@ -12,9 +12,9 @@ async function portPids(port) {
|
|
|
12
12
|
export async function freePort(port) {
|
|
13
13
|
try {
|
|
14
14
|
if (process.platform === 'win32') {
|
|
15
|
-
const { stdout } = await execFileAsync('netstat', ['-ano', '-p', 'tcp']);
|
|
15
|
+
const { stdout } = await execFileAsync('netstat.exe', ['-ano', '-p', 'tcp'], { windowsHide: true });
|
|
16
16
|
const pids = [...stdout.matchAll(new RegExp(`:${port}\\s+.*?LISTENING\\s+(\\d+)`, 'g'))].map((match) => match[1]);
|
|
17
|
-
await Promise.all([...new Set(pids)].map((pid) => execFileAsync('taskkill', ['/F', '/PID', pid])));
|
|
17
|
+
await Promise.all([...new Set(pids)].map((pid) => execFileAsync('taskkill.exe', ['/F', '/PID', pid], { windowsHide: true })));
|
|
18
18
|
} else {
|
|
19
19
|
const pids = await portPids(port);
|
|
20
20
|
await Promise.all(pids.map((pid) => execFileAsync('kill', ['-TERM', pid])));
|
|
@@ -2,7 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import crypto from 'node:crypto';
|
|
5
|
-
import {
|
|
5
|
+
import { execFileSync } from 'node:child_process';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Resolves standard bookmark file paths for Chrome and Edge based on OS.
|
|
@@ -37,11 +37,15 @@ export function checkBrowserRunning(browserName) {
|
|
|
37
37
|
try {
|
|
38
38
|
if (platform === 'win32') {
|
|
39
39
|
const exeName = browserName === 'chrome' ? 'chrome.exe' : 'msedge.exe';
|
|
40
|
-
const output =
|
|
40
|
+
const output = execFileSync('tasklist.exe', ['/FI', `IMAGENAME eq ${exeName}`], {
|
|
41
|
+
encoding: 'utf8',
|
|
42
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
43
|
+
windowsHide: true
|
|
44
|
+
});
|
|
41
45
|
return output.toLowerCase().includes(exeName.toLowerCase());
|
|
42
46
|
} else {
|
|
43
47
|
const processPattern = browserName === 'chrome' ? 'Google Chrome' : 'Microsoft Edge';
|
|
44
|
-
const output =
|
|
48
|
+
const output = execFileSync('pgrep', ['-f', processPattern], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true });
|
|
45
49
|
return output.trim().length > 0;
|
|
46
50
|
}
|
|
47
51
|
} catch {
|
|
@@ -24,7 +24,7 @@ export function openInSystemBrowser(url) {
|
|
|
24
24
|
safari: 'msedge'
|
|
25
25
|
};
|
|
26
26
|
const cmd = winCmdMap[browser] || 'chrome';
|
|
27
|
-
execFile('cmd', ['/c', 'start', '', cmd, url]);
|
|
27
|
+
execFile('cmd.exe', ['/c', 'start', '', cmd, url], { windowsHide: true });
|
|
28
28
|
} else {
|
|
29
29
|
execFile('xdg-open', [url]);
|
|
30
30
|
}
|
|
@@ -82,11 +82,11 @@ export function openInSystemEditor(path, location = '') {
|
|
|
82
82
|
}
|
|
83
83
|
} else if (process.platform === 'win32') {
|
|
84
84
|
if (editor === 'vscode') {
|
|
85
|
-
execFile('cmd', ['/c', 'code', '-n', path]);
|
|
85
|
+
execFile('cmd.exe', ['/c', 'code', '-n', path], { windowsHide: true });
|
|
86
86
|
} else if (editor === 'idea') {
|
|
87
|
-
execFile('cmd', ['/c', 'idea', path]);
|
|
87
|
+
execFile('cmd.exe', ['/c', 'idea', path], { windowsHide: true });
|
|
88
88
|
} else {
|
|
89
|
-
execFile('cmd', ['/c', 'start', '', path]);
|
|
89
|
+
execFile('cmd.exe', ['/c', 'start', '', path], { windowsHide: true });
|
|
90
90
|
}
|
|
91
91
|
} else {
|
|
92
92
|
if (editor === 'vscode') {
|
|
@@ -102,7 +102,7 @@ export function openInSystemFolder(targetPath) {
|
|
|
102
102
|
if (process.platform === 'darwin') {
|
|
103
103
|
execFile('open', [targetPath]);
|
|
104
104
|
} else if (process.platform === 'win32') {
|
|
105
|
-
execFile('cmd', ['/c', 'start', '', targetPath]);
|
|
105
|
+
execFile('cmd.exe', ['/c', 'start', '', targetPath], { windowsHide: true });
|
|
106
106
|
} else {
|
|
107
107
|
execFile('xdg-open', [targetPath]);
|
|
108
108
|
}
|
|
@@ -126,7 +126,7 @@ export async function openInSystemTerminal(targetPath) {
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
if (process.platform === 'win32') {
|
|
129
|
-
await execFileAsync('powershell.exe', ['-NoProfile', '-Command', 'Start-Process', 'cmd.exe', '-WorkingDirectory', targetPath]);
|
|
129
|
+
await execFileAsync('powershell.exe', ['-NoProfile', '-WindowStyle', 'Hidden', '-Command', 'Start-Process', 'cmd.exe', '-WorkingDirectory', targetPath], { windowsHide: true });
|
|
130
130
|
return;
|
|
131
131
|
}
|
|
132
132
|
|
|
@@ -143,7 +143,7 @@ async function getClipboardText() {
|
|
|
143
143
|
const commands = process.platform === 'darwin'
|
|
144
144
|
? [['pbpaste', []]]
|
|
145
145
|
: process.platform === 'win32'
|
|
146
|
-
? [['powershell.exe', ['-NoProfile', '-Command', 'Get-Clipboard -Format Text -Raw']]]
|
|
146
|
+
? [['powershell.exe', ['-NoProfile', '-WindowStyle', 'Hidden', '-Command', 'Get-Clipboard -Format Text -Raw']]]
|
|
147
147
|
: [
|
|
148
148
|
['wl-paste', ['--no-newline', '--type', 'text']],
|
|
149
149
|
['xclip', ['-selection', 'clipboard', '-o', '-t', 'UTF8_STRING']],
|
|
@@ -32,7 +32,8 @@ function getEnrichedEnv() {
|
|
|
32
32
|
} catch {}
|
|
33
33
|
}
|
|
34
34
|
const currentPath = process.env.PATH || '';
|
|
35
|
-
const
|
|
35
|
+
const pathSeparator = process.platform === 'win32' ? ';' : ':';
|
|
36
|
+
const mergedPath = [...new Set([...extraPaths, ...currentPath.split(pathSeparator)])].filter(Boolean).join(pathSeparator);
|
|
36
37
|
return { ...process.env, PATH: mergedPath };
|
|
37
38
|
}
|
|
38
39
|
|
|
@@ -56,8 +57,17 @@ function parseJsonOutput(text) {
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
async function run(command, args, timeout = 5000) {
|
|
60
|
+
const executable = process.platform === 'win32'
|
|
61
|
+
? ({ npm: 'npm.cmd', node: 'node.exe', git: 'git.exe' }[command] || command)
|
|
62
|
+
: command;
|
|
59
63
|
try {
|
|
60
|
-
const { stdout } = await execFileAsync(
|
|
64
|
+
const { stdout } = await execFileAsync(executable, args, {
|
|
65
|
+
timeout,
|
|
66
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
67
|
+
env: getEnrichedEnv(),
|
|
68
|
+
shell: process.platform === 'win32' && executable.endsWith('.cmd'),
|
|
69
|
+
windowsHide: process.platform === 'win32'
|
|
70
|
+
});
|
|
61
71
|
return stdout.trim();
|
|
62
72
|
} catch (error) {
|
|
63
73
|
if (['node', 'npm'].includes(command)) {
|
|
@@ -71,9 +81,12 @@ async function run(command, args, timeout = 5000) {
|
|
|
71
81
|
}
|
|
72
82
|
|
|
73
83
|
async function runNvm(script, timeout = 5000) {
|
|
84
|
+
if (process.platform === 'win32') {
|
|
85
|
+
throw new Error('Unix NVM shell is not supported on Windows.');
|
|
86
|
+
}
|
|
74
87
|
for (const shell of ['zsh', 'bash']) {
|
|
75
88
|
try {
|
|
76
|
-
const { stdout } = await execFileAsync(shell, ['-ilc', `${NVM_LOAD}${script}`], { timeout, maxBuffer: 4 * 1024 * 1024, env: getEnrichedEnv() });
|
|
89
|
+
const { stdout } = await execFileAsync(shell, ['-ilc', `${NVM_LOAD}${script}`], { timeout, maxBuffer: 4 * 1024 * 1024, env: getEnrichedEnv(), windowsHide: false });
|
|
77
90
|
return stdout.trim();
|
|
78
91
|
} catch {}
|
|
79
92
|
}
|
|
@@ -267,4 +280,3 @@ export async function installNvmVersion(version) {
|
|
|
267
280
|
await runNvm(`nvm install ${shellQuote(target)}`, 10 * 60 * 1000);
|
|
268
281
|
return readNvmConfiguration();
|
|
269
282
|
}
|
|
270
|
-
|
|
@@ -25,7 +25,7 @@ export async function selectDirectory() {
|
|
|
25
25
|
Write-Output $dialog.SelectedPath
|
|
26
26
|
}
|
|
27
27
|
`;
|
|
28
|
-
const { stdout } = await execFileAsync('powershell', ['-NoProfile', '-Command', psScript]);
|
|
28
|
+
const { stdout } = await execFileAsync('powershell.exe', ['-NoProfile', '-WindowStyle', 'Hidden', '-Command', psScript], { windowsHide: true });
|
|
29
29
|
const dirPath = stdout.trim();
|
|
30
30
|
return dirPath ? { path: dirPath, canceled: false } : { path: null, canceled: true };
|
|
31
31
|
} catch {
|
|
@@ -64,7 +64,7 @@ export async function selectFile() {
|
|
|
64
64
|
Write-Output $dialog.FileName
|
|
65
65
|
}
|
|
66
66
|
`;
|
|
67
|
-
const { stdout } = await execFileAsync('powershell', ['-NoProfile', '-Command', psScript]);
|
|
67
|
+
const { stdout } = await execFileAsync('powershell.exe', ['-NoProfile', '-WindowStyle', 'Hidden', '-Command', psScript], { windowsHide: true });
|
|
68
68
|
const filePath = stdout.trim();
|
|
69
69
|
return filePath ? { path: filePath, canceled: false } : { path: null, canceled: true };
|
|
70
70
|
} catch {
|
|
@@ -114,7 +114,7 @@ export function clearScriptLogs(launcherId, scriptId) { logs.set(keyFor(launcher
|
|
|
114
114
|
async function listeningProcesses() {
|
|
115
115
|
if (process.platform === 'win32') {
|
|
116
116
|
try {
|
|
117
|
-
const { stdout } = await execFileAsync('netstat', ['-ano', '-p', 'tcp']);
|
|
117
|
+
const { stdout } = await execFileAsync('netstat.exe', ['-ano', '-p', 'tcp'], { windowsHide: true });
|
|
118
118
|
const rows = [];
|
|
119
119
|
const matches = stdout.matchAll(/TCP\s+(?:\[::\]|[\d.]+):(\d+)\s+.*?LISTENING\s+(\d+)/gi);
|
|
120
120
|
for (const m of matches) {
|
|
@@ -151,6 +151,9 @@ async function listeningProcesses() {
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
async function processGroupFor(pid) {
|
|
154
|
+
// Windows has no Unix process-group ID. Do not attempt to invoke `ps` here;
|
|
155
|
+
// on some installations that resolves through a visible shell window.
|
|
156
|
+
if (process.platform === 'win32') return null;
|
|
154
157
|
try {
|
|
155
158
|
const { stdout } = await execFileAsync('ps', ['-o', 'pgid=', '-p', String(pid)]);
|
|
156
159
|
return Number(stdout.trim());
|
|
@@ -253,7 +256,7 @@ export function runScript(launcher, script) {
|
|
|
253
256
|
if (running.has(key)) throw new Error('This script is already running.');
|
|
254
257
|
if (!existsSync(launcher.folder)) throw new Error('The configured project folder does not exist.');
|
|
255
258
|
logs.set(key, { output: `$ ${script.command}\n`, error: '', errorCount: 0 });
|
|
256
|
-
const child = spawn(process.execPath, [supervisorPath], { cwd: launcher.folder, detached: process.platform !== 'win32', stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env, ...(launcher.executor === 'pnpm' || /^\s*pnpm\b/.test(script.command) ? { CI: 'true' } : {}), BUDDY_PARENT_PID: String(process.pid), BUDDY_SCRIPT_COMMAND: script.command } });
|
|
259
|
+
const child = spawn(process.execPath, [supervisorPath], { cwd: launcher.folder, detached: process.platform !== 'win32', stdio: ['ignore', 'pipe', 'pipe'], windowsHide: process.platform === 'win32', env: { ...process.env, ...(launcher.executor === 'pnpm' || /^\s*pnpm\b/.test(script.command) ? { CI: 'true' } : {}), BUDDY_PARENT_PID: String(process.pid), BUDDY_SCRIPT_COMMAND: script.command } });
|
|
257
260
|
child.stdout.on('data', (chunk) => appendLog(key, 'output', chunk.toString()));
|
|
258
261
|
child.stderr.on('data', (chunk) => appendLog(key, 'error', chunk.toString()));
|
|
259
262
|
running.set(key, child);
|
|
@@ -4,7 +4,12 @@ import { promisify } from 'node:util';
|
|
|
4
4
|
const execFileAsync = promisify(execFile);
|
|
5
5
|
const parentPid = Number(process.env.BUDDY_PARENT_PID);
|
|
6
6
|
const command = process.env.BUDDY_SCRIPT_COMMAND;
|
|
7
|
-
const child = spawn(command, {
|
|
7
|
+
const child = spawn(command, {
|
|
8
|
+
cwd: process.cwd(),
|
|
9
|
+
shell: true,
|
|
10
|
+
stdio: 'inherit',
|
|
11
|
+
windowsHide: process.platform === 'win32'
|
|
12
|
+
});
|
|
8
13
|
|
|
9
14
|
function stopGroup() {
|
|
10
15
|
if (process.platform === 'win32') { child.kill('SIGTERM'); return; }
|