@yeaft/webchat-agent 0.1.15 → 0.1.16
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 +78 -7
- package/connection/upgrade-worker-template.js +19 -3
- package/package.json +1 -1
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
|
|
140
|
-
if (
|
|
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 ${
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|