@yeaft/webchat-agent 0.1.15 → 0.1.17

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/cli.js CHANGED
@@ -3,10 +3,11 @@
3
3
  * CLI entry point for @yeaft/webchat-agent
4
4
  * Parses command-line arguments and starts the agent or runs subcommands
5
5
  */
6
- import { execSync } from 'child_process';
7
- import { readFileSync } from 'fs';
6
+ import { execSync, spawn } from 'child_process';
7
+ import { readFileSync, writeFileSync, mkdirSync } from 'fs';
8
8
  import { dirname, join } from 'path';
9
9
  import { fileURLToPath } from 'url';
10
+ import { platform, homedir } from 'os';
10
11
 
11
12
  const __dirname = dirname(fileURLToPath(import.meta.url));
12
13
  const pkg = JSON.parse(readFileSync(join(__dirname, 'package.json'), 'utf-8'));
@@ -136,16 +137,86 @@ function upgrade() {
136
137
  console.log('Checking for updates...');
137
138
 
138
139
  try {
139
- const res = execSync(`npm view ${pkg.name} version`, { encoding: 'utf-8' }).trim();
140
- if (res === pkg.version) {
140
+ const latest = execSync(`npm view ${pkg.name} version`, { encoding: 'utf-8' }).trim();
141
+ if (latest === pkg.version) {
141
142
  console.log('Already up to date.');
142
143
  return;
143
144
  }
144
- console.log(`Upgrading to ${res}...`);
145
- execSync(`npm install -g ${pkg.name}@latest`, { stdio: 'inherit' });
146
- console.log(`Successfully upgraded to ${res}`);
145
+ console.log(`Upgrading to ${latest}...`);
146
+
147
+ if (platform() === 'win32') {
148
+ // On Windows, the current process locks its own files. npm cannot overwrite
149
+ // them while this process is running. Spawn a detached bat script that waits
150
+ // for us to exit, then runs npm install, then optionally restarts the service.
151
+ upgradeWindows(latest);
152
+ } else {
153
+ execSync(`npm install -g ${pkg.name}@latest`, { stdio: 'inherit' });
154
+ console.log(`Successfully upgraded to ${latest}`);
155
+ }
147
156
  } catch (e) {
148
157
  console.error('Upgrade failed:', e.message);
149
158
  process.exit(1);
150
159
  }
151
160
  }
161
+
162
+ function upgradeWindows(latestVersion) {
163
+ const configDir = join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'yeaft-agent');
164
+ mkdirSync(configDir, { recursive: true });
165
+ const logDir = join(configDir, 'logs');
166
+ mkdirSync(logDir, { recursive: true });
167
+ const batPath = join(configDir, 'upgrade-cli.bat');
168
+ const logPath = join(logDir, 'upgrade.log');
169
+ const pid = process.pid;
170
+ const pkgSpec = `${pkg.name}@${latestVersion}`;
171
+
172
+ const batLines = [
173
+ '@echo off',
174
+ 'setlocal',
175
+ `set PID=${pid}`,
176
+ `set PKG=${pkgSpec}`,
177
+ `set LOGFILE=${logPath}`,
178
+ `set MAX_WAIT=30`,
179
+ `set COUNT=0`,
180
+ '',
181
+ ':: Change to temp dir to avoid EBUSY on cwd',
182
+ 'cd /d "%TEMP%"',
183
+ '',
184
+ 'echo [Upgrade] Started at %date% %time% > "%LOGFILE%"',
185
+ 'echo [Upgrade] Waiting for CLI process (PID %PID%) to exit... >> "%LOGFILE%"',
186
+ '',
187
+ ':WAIT_LOOP',
188
+ 'tasklist /FI "PID eq %PID%" 2>NUL | find /I "%PID%" >NUL',
189
+ 'if errorlevel 1 goto PID_EXITED',
190
+ 'set /A COUNT+=1',
191
+ 'if %COUNT% GEQ %MAX_WAIT% (',
192
+ ' echo [Upgrade] Timeout waiting for PID %PID% to exit >> "%LOGFILE%"',
193
+ ' goto PID_EXITED',
194
+ ')',
195
+ 'ping -n 3 127.0.0.1 >NUL',
196
+ 'goto WAIT_LOOP',
197
+ ':PID_EXITED',
198
+ '',
199
+ 'echo [Upgrade] Process exited, running npm install -g %PKG%... >> "%LOGFILE%"',
200
+ 'call npm install -g %PKG% >> "%LOGFILE%" 2>&1',
201
+ 'if not "%errorlevel%"=="0" (',
202
+ ' echo [Upgrade] npm install failed with exit code %errorlevel% >> "%LOGFILE%"',
203
+ ' goto END',
204
+ ')',
205
+ 'echo [Upgrade] Successfully upgraded. >> "%LOGFILE%"',
206
+ '',
207
+ ':END',
208
+ `del /F /Q "${batPath}" 2>NUL`,
209
+ ];
210
+
211
+ writeFileSync(batPath, batLines.join('\r\n'));
212
+ const child = spawn('cmd.exe', ['/c', batPath], {
213
+ detached: true,
214
+ stdio: 'ignore',
215
+ windowsHide: true,
216
+ });
217
+ child.unref();
218
+ console.log(`Upgrade script spawned. This process will exit now.`);
219
+ console.log(`The upgrade will proceed after this process exits.`);
220
+ console.log(`Check upgrade log: ${logPath}`);
221
+ process.exit(0);
222
+ }
@@ -15,6 +15,22 @@ function log(msg) {
15
15
  try { fs.appendFileSync(LOGFILE, line + '\n'); } catch {}
16
16
  }
17
17
 
18
+ // Retry a file operation with exponential backoff (Windows file lock workaround)
19
+ function retryOp(fn, label, maxRetries = 5) {
20
+ for (let i = 0; i <= maxRetries; i++) {
21
+ try {
22
+ return fn();
23
+ } catch (err) {
24
+ const isLockErr = err.code === 'EBUSY' || err.code === 'EPERM' || err.code === 'EACCES';
25
+ if (!isLockErr || i === maxRetries) throw err;
26
+ const delay = Math.min(1000 * Math.pow(2, i), 10000);
27
+ log(`${label}: ${err.code}, retrying in ${delay}ms (${i + 1}/${maxRetries})...`);
28
+ const end = Date.now() + delay;
29
+ while (Date.now() < end) { /* busy-wait in sync context */ }
30
+ }
31
+ }
32
+ }
33
+
18
34
  function parseTar(buf) {
19
35
  const files = [];
20
36
  let offset = 0;
@@ -46,9 +62,9 @@ function rmDirContents(dir, keep) {
46
62
  const stat = fs.statSync(full, { throwIfNoEntry: false });
47
63
  if (!stat) continue;
48
64
  if (stat.isDirectory()) {
49
- fs.rmSync(full, { recursive: true, force: true });
65
+ retryOp(() => fs.rmSync(full, { recursive: true, force: true }), 'rmdir ' + entry);
50
66
  } else {
51
- fs.unlinkSync(full);
67
+ retryOp(() => fs.unlinkSync(full), 'unlink ' + entry);
52
68
  }
53
69
  }
54
70
  }
@@ -77,7 +93,7 @@ try {
77
93
  for (const f of files) {
78
94
  const dest = path.join(TARGET, f.path);
79
95
  fs.mkdirSync(path.dirname(dest), { recursive: true });
80
- fs.writeFileSync(dest, f.data);
96
+ retryOp(() => fs.writeFileSync(dest, f.data), 'write ' + f.path);
81
97
  }
82
98
  log('Copied ' + files.length + ' files to target');
83
99
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",