buddy-workbench 0.1.54 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { execFileSync, execSync, spawn } from 'node:child_process';
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
- throw new Error(`Unable to query the latest ${packageName} version from npm.`);
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
- throw new Error(`Unable to install ${details.name}@${version}. Check npm permissions and network access.`);
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
- execSync(`kill -${signal === 'SIGTERM' ? '15' : '9'} ${pid}`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buddy-workbench",
3
- "version": "0.1.54",
3
+ "version": "0.1.55",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -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 { execSync } from 'node:child_process';
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 = execSync(`tasklist /FI "IMAGENAME eq ${exeName}"`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
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 = execSync(`pgrep -f "${processPattern}"`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
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 {
@@ -32,7 +32,8 @@ function getEnrichedEnv() {
32
32
  } catch {}
33
33
  }
34
34
  const currentPath = process.env.PATH || '';
35
- const mergedPath = [...new Set([...extraPaths, ...currentPath.split(':')])].filter(Boolean).join(':');
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(command, args, { timeout, maxBuffer: 8 * 1024 * 1024, env: getEnrichedEnv() });
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
-
@@ -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());