termbeam 1.17.1 → 1.17.3
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/package.json +1 -1
- package/src/cli/service.js +139 -54
- package/src/server/routes.js +3 -2
- package/src/utils/update-check.js +39 -1
- package/src/utils/update-executor.js +29 -2
package/package.json
CHANGED
package/src/cli/service.js
CHANGED
|
@@ -46,6 +46,7 @@ function installPm2Global() {
|
|
|
46
46
|
execFileSync('npm', ['install', '-g', 'pm2'], {
|
|
47
47
|
stdio: 'inherit',
|
|
48
48
|
timeout: 120000,
|
|
49
|
+
shell: os.platform() === 'win32',
|
|
49
50
|
});
|
|
50
51
|
console.log(green('✓ PM2 installed successfully.\n'));
|
|
51
52
|
return true;
|
|
@@ -122,6 +123,20 @@ function writeEcosystem(content) {
|
|
|
122
123
|
fs.writeFileSync(ECOSYSTEM_FILE, content, 'utf8');
|
|
123
124
|
}
|
|
124
125
|
|
|
126
|
+
function readEcosystemName() {
|
|
127
|
+
try {
|
|
128
|
+
const content = fs.readFileSync(ECOSYSTEM_FILE, 'utf8');
|
|
129
|
+
const json = content.replace(/^module\.exports\s*=\s*/, '').replace(/;\s*$/, '');
|
|
130
|
+
const eco = JSON.parse(json);
|
|
131
|
+
if (eco.apps && eco.apps[0] && eco.apps[0].name) {
|
|
132
|
+
return eco.apps[0].name;
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
// ecosystem file missing or malformed
|
|
136
|
+
}
|
|
137
|
+
return DEFAULT_SERVICE_NAME;
|
|
138
|
+
}
|
|
139
|
+
|
|
125
140
|
// ── PM2 Commands ─────────────────────────────────────────────────────────────
|
|
126
141
|
|
|
127
142
|
function pm2Exec(args, opts = {}) {
|
|
@@ -131,6 +146,8 @@ function pm2Exec(args, opts = {}) {
|
|
|
131
146
|
encoding: 'utf8',
|
|
132
147
|
stdio: opts.inherit ? 'inherit' : ['pipe', 'pipe', 'pipe'],
|
|
133
148
|
timeout: 30000,
|
|
149
|
+
// Windows npm globals are .cmd wrappers; execFileSync needs shell to resolve them
|
|
150
|
+
shell: os.platform() === 'win32',
|
|
134
151
|
...opts,
|
|
135
152
|
});
|
|
136
153
|
} catch (err) {
|
|
@@ -225,6 +242,7 @@ async function actionInstall() {
|
|
|
225
242
|
|
|
226
243
|
// Service name
|
|
227
244
|
showProgress(0);
|
|
245
|
+
console.log(dim(' The PM2 process name for this service.\n'));
|
|
228
246
|
config.name = await ask(rl, 'Service name:', DEFAULT_SERVICE_NAME);
|
|
229
247
|
decisions.push({ label: 'Service name', value: config.name });
|
|
230
248
|
|
|
@@ -424,62 +442,108 @@ async function actionInstall() {
|
|
|
424
442
|
// Run pm2 startup if chosen during wizard
|
|
425
443
|
if (config.startup) {
|
|
426
444
|
console.log('');
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
// pm2 startup exits 1 by design — the sudo command is in stdout
|
|
438
|
-
startupOutput = (err.stdout || '') + (err.stderr || '');
|
|
439
|
-
}
|
|
440
|
-
const sudoMatch = startupOutput.match(/^(sudo .+)$/m);
|
|
441
|
-
if (sudoMatch) {
|
|
442
|
-
console.log(dim('Setting up boot persistence (may ask for your password)...\n'));
|
|
443
|
-
const { spawnSync } = require('child_process');
|
|
444
|
-
// pm2 outputs: sudo env PATH=$PATH:/extra /path/to/pm2 startup <init> -u <user> --hp <home>
|
|
445
|
-
// We can't use sh -c because $PATH may contain spaces (e.g. "Visual Studio Code.app").
|
|
446
|
-
// Instead, parse the structured command and pass PATH via env to avoid shell expansion.
|
|
447
|
-
const envMatch = sudoMatch[1].match(
|
|
448
|
-
/^sudo\s+env\s+PATH=\$PATH:([\S]+)\s+(\S+)\s+startup\s+(.+)$/,
|
|
445
|
+
if (os.platform() === 'win32') {
|
|
446
|
+
// Windows: pm2 startup doesn't support Windows init systems.
|
|
447
|
+
// Instead, create a script in the Windows Startup folder that runs pm2 resurrect.
|
|
448
|
+
const startupDir = path.join(
|
|
449
|
+
process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'),
|
|
450
|
+
'Microsoft',
|
|
451
|
+
'Windows',
|
|
452
|
+
'Start Menu',
|
|
453
|
+
'Programs',
|
|
454
|
+
'Startup',
|
|
449
455
|
);
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
const pm2Bin = envMatch[2]; // e.g. /opt/homebrew/.../pm2
|
|
454
|
-
const restArgs = envMatch[3].split(/\s+/); // e.g. ['launchd', '-u', 'user', '--hp', '/home']
|
|
455
|
-
const fullPath = (process.env.PATH || '') + ':' + extraPath;
|
|
456
|
-
result = spawnSync('sudo', ['env', `PATH=${fullPath}`, pm2Bin, 'startup', ...restArgs], {
|
|
457
|
-
stdio: 'inherit',
|
|
458
|
-
});
|
|
459
|
-
} else {
|
|
460
|
-
// Fallback: try running via sh with quoted PATH
|
|
461
|
-
const resolved = sudoMatch[1].replace(/\$PATH/g, `'${process.env.PATH || ''}'`);
|
|
462
|
-
result = spawnSync('sh', ['-c', resolved], { stdio: 'inherit' });
|
|
463
|
-
}
|
|
464
|
-
const code = result.status;
|
|
465
|
-
if (code === 0) {
|
|
456
|
+
const startupScript = path.join(startupDir, 'termbeam-pm2.cmd');
|
|
457
|
+
try {
|
|
458
|
+
fs.writeFileSync(startupScript, '@echo off\r\npm2 resurrect\r\n', 'utf8');
|
|
466
459
|
pm2Exec(['save'], { inherit: true });
|
|
467
460
|
console.log(green('✓ TermBeam will start automatically on boot.'));
|
|
468
|
-
|
|
469
|
-
|
|
461
|
+
console.log(dim(` Startup script: ${startupScript}`));
|
|
462
|
+
} catch (err) {
|
|
463
|
+
console.error(red('✗ Failed to create startup script.'));
|
|
470
464
|
console.log(yellow(" TermBeam is running, but won't auto-start after a reboot."));
|
|
471
|
-
console.log(yellow(' To fix this,
|
|
472
|
-
console.log(` ${cyan(
|
|
465
|
+
console.log(yellow(' To fix this manually, create a file at:\n'));
|
|
466
|
+
console.log(` ${cyan(startupScript)}`);
|
|
467
|
+
console.log(yellow('\n With contents:'));
|
|
468
|
+
console.log(` ${cyan('@echo off & pm2 resurrect')}`);
|
|
473
469
|
console.log(yellow('\n Then run:'));
|
|
474
470
|
console.log(` ${cyan('pm2 save')}\n`);
|
|
475
471
|
}
|
|
476
472
|
} else {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
473
|
+
// Unix/macOS/WSL: try pm2 startup to set up boot persistence
|
|
474
|
+
let startupOutput = '';
|
|
475
|
+
let succeeded = false;
|
|
476
|
+
let initSystemError = false;
|
|
477
|
+
try {
|
|
478
|
+
startupOutput = execFileSync('pm2', ['startup'], {
|
|
479
|
+
encoding: 'utf8',
|
|
480
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
481
|
+
timeout: 15000,
|
|
482
|
+
});
|
|
483
|
+
// Exit 0 means pm2 configured startup directly (e.g. running as root)
|
|
484
|
+
succeeded = true;
|
|
485
|
+
} catch (err) {
|
|
486
|
+
// pm2 startup exits 1 by design — the sudo command is in stdout
|
|
487
|
+
startupOutput = (err.stdout || '') + (err.stderr || '');
|
|
488
|
+
if (startupOutput.includes('Init system not found')) {
|
|
489
|
+
initSystemError = true;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (succeeded) {
|
|
494
|
+
// pm2 startup succeeded (typically running as root) — just save
|
|
495
|
+
pm2Exec(['save'], { inherit: true });
|
|
496
|
+
console.log(green('✓ TermBeam will start automatically on boot.'));
|
|
497
|
+
} else if (initSystemError) {
|
|
498
|
+
// WSL without systemd, or other environments without an init system
|
|
499
|
+
console.log(yellow('⚠ No init system detected (common in WSL without systemd).'));
|
|
500
|
+
console.log(yellow(" TermBeam is running, but won't auto-start after a reboot."));
|
|
501
|
+
console.log(dim(' To enable boot persistence, either:'));
|
|
502
|
+
console.log(dim(' • Enable systemd in WSL: add [boot] systemd=true to /etc/wsl.conf'));
|
|
503
|
+
console.log(dim(' • Or add "pm2 resurrect" to your shell profile (~/.bashrc)\n'));
|
|
504
|
+
} else {
|
|
505
|
+
const sudoMatch = startupOutput.match(/^(sudo .+)$/m);
|
|
506
|
+
if (sudoMatch) {
|
|
507
|
+
console.log(dim('Setting up boot persistence (may ask for your password)...\n'));
|
|
508
|
+
const { spawnSync } = require('child_process');
|
|
509
|
+
const envMatch = sudoMatch[1].match(
|
|
510
|
+
/^sudo\s+env\s+PATH=\$PATH:([\S]+)\s+(\S+)\s+startup\s+(.+)$/,
|
|
511
|
+
);
|
|
512
|
+
let result;
|
|
513
|
+
if (envMatch) {
|
|
514
|
+
const extraPath = envMatch[1];
|
|
515
|
+
const pm2Bin = envMatch[2];
|
|
516
|
+
const restArgs = envMatch[3].split(/\s+/);
|
|
517
|
+
const fullPath = (process.env.PATH || '') + ':' + extraPath;
|
|
518
|
+
result = spawnSync(
|
|
519
|
+
'sudo',
|
|
520
|
+
['env', `PATH=${fullPath}`, pm2Bin, 'startup', ...restArgs],
|
|
521
|
+
{ stdio: 'inherit' },
|
|
522
|
+
);
|
|
523
|
+
} else {
|
|
524
|
+
const resolved = sudoMatch[1].replace(/\$PATH/g, `'${process.env.PATH || ''}'`);
|
|
525
|
+
result = spawnSync('sh', ['-c', resolved], { stdio: 'inherit' });
|
|
526
|
+
}
|
|
527
|
+
if (result.status === 0) {
|
|
528
|
+
pm2Exec(['save'], { inherit: true });
|
|
529
|
+
console.log(green('✓ TermBeam will start automatically on boot.'));
|
|
530
|
+
} else {
|
|
531
|
+
console.error(red('\n✗ Failed to set up boot persistence.'));
|
|
532
|
+
console.log(yellow(" TermBeam is running, but won't auto-start after a reboot."));
|
|
533
|
+
console.log(yellow(' To fix this, run the following command manually:\n'));
|
|
534
|
+
console.log(` ${cyan(sudoMatch[1])}`);
|
|
535
|
+
console.log(yellow('\n Then run:'));
|
|
536
|
+
console.log(` ${cyan('pm2 save')}\n`);
|
|
537
|
+
}
|
|
538
|
+
} else {
|
|
539
|
+
console.error(red('✗ Could not determine boot persistence command.'));
|
|
540
|
+
console.log(yellow(" TermBeam is running, but won't auto-start after a reboot."));
|
|
541
|
+
console.log(yellow(' To fix this, run:\n'));
|
|
542
|
+
console.log(` ${cyan('pm2 startup')}`);
|
|
543
|
+
console.log(dim(' …then run the sudo command it outputs, followed by:'));
|
|
544
|
+
console.log(` ${cyan('pm2 save')}\n`);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
483
547
|
}
|
|
484
548
|
}
|
|
485
549
|
|
|
@@ -528,20 +592,22 @@ async function actionUninstall() {
|
|
|
528
592
|
process.exit(1);
|
|
529
593
|
}
|
|
530
594
|
|
|
531
|
-
//
|
|
595
|
+
// Determine service name: prefer ecosystem config, then PM2 process list, then default
|
|
596
|
+
const ecoName = readEcosystemName();
|
|
532
597
|
const list = pm2Exec(['jlist'], { silent: true });
|
|
533
598
|
let services = [];
|
|
534
599
|
if (list) {
|
|
535
600
|
try {
|
|
536
601
|
services = JSON.parse(list).filter(
|
|
537
|
-
(p) =>
|
|
602
|
+
(p) =>
|
|
603
|
+
p.name === ecoName || p.name === DEFAULT_SERVICE_NAME || p.name.startsWith('termbeam'),
|
|
538
604
|
);
|
|
539
605
|
} catch {
|
|
540
606
|
// ignore parse errors
|
|
541
607
|
}
|
|
542
608
|
}
|
|
543
609
|
|
|
544
|
-
const name = services.length > 0 ? services[0].name :
|
|
610
|
+
const name = services.length > 0 ? services[0].name : ecoName;
|
|
545
611
|
|
|
546
612
|
const rl = createRL();
|
|
547
613
|
const sure = await confirm(rl, `Remove TermBeam service "${name}" from PM2?`, true);
|
|
@@ -562,6 +628,23 @@ async function actionUninstall() {
|
|
|
562
628
|
console.log(dim(`Removed ${ECOSYSTEM_FILE}`));
|
|
563
629
|
}
|
|
564
630
|
|
|
631
|
+
// Clean up Windows startup script if present
|
|
632
|
+
if (os.platform() === 'win32') {
|
|
633
|
+
const startupScript = path.join(
|
|
634
|
+
process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'),
|
|
635
|
+
'Microsoft',
|
|
636
|
+
'Windows',
|
|
637
|
+
'Start Menu',
|
|
638
|
+
'Programs',
|
|
639
|
+
'Startup',
|
|
640
|
+
'termbeam-pm2.cmd',
|
|
641
|
+
);
|
|
642
|
+
if (fs.existsSync(startupScript)) {
|
|
643
|
+
fs.unlinkSync(startupScript);
|
|
644
|
+
console.log(dim(`Removed ${startupScript}`));
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
565
648
|
log.info('Service stopped');
|
|
566
649
|
console.log(green(`\n✓ TermBeam service "${name}" removed.\n`));
|
|
567
650
|
}
|
|
@@ -572,7 +655,7 @@ function actionStatus() {
|
|
|
572
655
|
console.error(red('✗ PM2 is not installed. Run: npm install -g pm2'));
|
|
573
656
|
process.exit(1);
|
|
574
657
|
}
|
|
575
|
-
pm2Exec(['describe',
|
|
658
|
+
pm2Exec(['describe', readEcosystemName()], { inherit: true });
|
|
576
659
|
}
|
|
577
660
|
|
|
578
661
|
function actionLogs() {
|
|
@@ -582,8 +665,9 @@ function actionLogs() {
|
|
|
582
665
|
process.exit(1);
|
|
583
666
|
}
|
|
584
667
|
const { spawn } = require('child_process');
|
|
585
|
-
const child = spawn('pm2', ['logs',
|
|
668
|
+
const child = spawn('pm2', ['logs', readEcosystemName(), '--lines', '200'], {
|
|
586
669
|
stdio: 'inherit',
|
|
670
|
+
shell: os.platform() === 'win32',
|
|
587
671
|
});
|
|
588
672
|
child.on('error', (err) => {
|
|
589
673
|
console.error(red(`✗ Failed to stream logs: ${err.message}`));
|
|
@@ -596,7 +680,7 @@ function actionRestart() {
|
|
|
596
680
|
console.error(red('✗ PM2 is not installed. Run: npm install -g pm2'));
|
|
597
681
|
process.exit(1);
|
|
598
682
|
}
|
|
599
|
-
pm2Exec(['restart',
|
|
683
|
+
pm2Exec(['restart', readEcosystemName()], { inherit: true });
|
|
600
684
|
log.info('Service restarted');
|
|
601
685
|
console.log(green('\n✓ TermBeam service restarted.\n'));
|
|
602
686
|
}
|
|
@@ -651,6 +735,7 @@ module.exports = {
|
|
|
651
735
|
buildArgs,
|
|
652
736
|
generateEcosystem,
|
|
653
737
|
writeEcosystem,
|
|
738
|
+
readEcosystemName,
|
|
654
739
|
pm2Exec,
|
|
655
740
|
actionStatus,
|
|
656
741
|
actionRestart,
|
package/src/server/routes.js
CHANGED
|
@@ -101,12 +101,12 @@ function setupRoutes(app, { auth, sessions, config, state, pushManager }) {
|
|
|
101
101
|
|
|
102
102
|
try {
|
|
103
103
|
const info = await checkForUpdate({ currentVersion: config.version, force });
|
|
104
|
-
const { installCmd, installArgs, ...publicInstallInfo } = detectInstallMethod();
|
|
104
|
+
const { installCmd, installArgs, cwd, ...publicInstallInfo } = detectInstallMethod();
|
|
105
105
|
state.updateInfo = { ...info, ...publicInstallInfo };
|
|
106
106
|
res.json(state.updateInfo);
|
|
107
107
|
} catch (err) {
|
|
108
108
|
log.warn(`Update check failed: ${err.message}`);
|
|
109
|
-
const { installCmd, installArgs, ...publicInstallInfo } = detectInstallMethod();
|
|
109
|
+
const { installCmd, installArgs, cwd, ...publicInstallInfo } = detectInstallMethod();
|
|
110
110
|
const fallback = {
|
|
111
111
|
current: config.version,
|
|
112
112
|
latest: null,
|
|
@@ -226,6 +226,7 @@ function setupRoutes(app, { auth, sessions, config, state, pushManager }) {
|
|
|
226
226
|
restartStrategy: installInfo.restartStrategy,
|
|
227
227
|
onProgress: broadcastProgress,
|
|
228
228
|
performRestart,
|
|
229
|
+
cwd: installInfo.cwd,
|
|
229
230
|
}).catch((err) => {
|
|
230
231
|
log.error(`Update execution error: ${err.message}`);
|
|
231
232
|
});
|
|
@@ -292,14 +292,31 @@ function detectInstallMethod() {
|
|
|
292
292
|
// Check before Docker: a git checkout running inside a container (CI/devcontainers)
|
|
293
293
|
// should be treated as source, not Docker
|
|
294
294
|
if (isRunningFromSource()) {
|
|
295
|
+
const sourceRoot = getSourceRoot();
|
|
296
|
+
const baseCmd = 'git pull && npm install && npm run build:frontend';
|
|
297
|
+
|
|
298
|
+
if (isPm2) {
|
|
299
|
+
log.debug('Install method: source (PM2)');
|
|
300
|
+
return {
|
|
301
|
+
method: 'source',
|
|
302
|
+
command: `${baseCmd} && pm2 restart termbeam`,
|
|
303
|
+
installCmd: process.platform === 'win32' ? process.env.COMSPEC || 'cmd.exe' : 'sh',
|
|
304
|
+
installArgs: process.platform === 'win32' ? ['/c', baseCmd] : ['-c', baseCmd],
|
|
305
|
+
canAutoUpdate: true,
|
|
306
|
+
restartStrategy: 'pm2',
|
|
307
|
+
cwd: sourceRoot,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
295
311
|
log.debug('Install method: source');
|
|
296
312
|
return {
|
|
297
313
|
method: 'source',
|
|
298
|
-
command:
|
|
314
|
+
command: baseCmd,
|
|
299
315
|
installCmd: null,
|
|
300
316
|
installArgs: null,
|
|
301
317
|
canAutoUpdate: false,
|
|
302
318
|
restartStrategy: 'none',
|
|
319
|
+
cwd: sourceRoot,
|
|
303
320
|
};
|
|
304
321
|
}
|
|
305
322
|
|
|
@@ -346,6 +363,26 @@ function isRunningInDocker() {
|
|
|
346
363
|
return false;
|
|
347
364
|
}
|
|
348
365
|
|
|
366
|
+
/**
|
|
367
|
+
* Find the root of the source checkout by walking up from __dirname.
|
|
368
|
+
* Returns the absolute path to the repo root, or null if not found.
|
|
369
|
+
*/
|
|
370
|
+
function getSourceRoot() {
|
|
371
|
+
if (__dirname.includes('node_modules')) return null;
|
|
372
|
+
try {
|
|
373
|
+
let currentDir = __dirname;
|
|
374
|
+
for (let i = 0; i < 10; i++) {
|
|
375
|
+
if (fs.existsSync(path.join(currentDir, '.git'))) return currentDir;
|
|
376
|
+
const parentDir = path.dirname(currentDir);
|
|
377
|
+
if (!parentDir || parentDir === currentDir) break;
|
|
378
|
+
currentDir = parentDir;
|
|
379
|
+
}
|
|
380
|
+
} catch {
|
|
381
|
+
// ignore
|
|
382
|
+
}
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
|
|
349
386
|
/**
|
|
350
387
|
* Detect if running from a git source checkout (not installed as a package).
|
|
351
388
|
* Walks upward from __dirname looking for .git to avoid fragile fixed-depth assumptions.
|
|
@@ -387,4 +424,5 @@ module.exports = {
|
|
|
387
424
|
isRunningInDocker,
|
|
388
425
|
isRunningFromSource,
|
|
389
426
|
isRunningUnderPm2,
|
|
427
|
+
getSourceRoot,
|
|
390
428
|
};
|
|
@@ -71,6 +71,16 @@ function resetState() {
|
|
|
71
71
|
* Returns { canUpdate, reason } — if canUpdate is false, reason explains why.
|
|
72
72
|
*/
|
|
73
73
|
async function checkPermissions(method) {
|
|
74
|
+
// Source installs use git, not a package manager
|
|
75
|
+
if (method === 'source') {
|
|
76
|
+
try {
|
|
77
|
+
await execFilePromise('git', ['--version'], { timeout: VERIFY_TIMEOUT_MS });
|
|
78
|
+
} catch {
|
|
79
|
+
return { canUpdate: false, reason: 'git not found on PATH' };
|
|
80
|
+
}
|
|
81
|
+
return { canUpdate: true, reason: null };
|
|
82
|
+
}
|
|
83
|
+
|
|
74
84
|
const cmd = method === 'yarn' ? 'yarn' : method === 'pnpm' ? 'pnpm' : 'npm';
|
|
75
85
|
|
|
76
86
|
// Check if the package manager is available by running it directly
|
|
@@ -124,6 +134,7 @@ async function executeUpdate({
|
|
|
124
134
|
restartStrategy,
|
|
125
135
|
onProgress,
|
|
126
136
|
performRestart,
|
|
137
|
+
cwd,
|
|
127
138
|
}) {
|
|
128
139
|
if (updateState.status !== 'idle' && updateState.status !== 'failed') {
|
|
129
140
|
return { ...updateState, error: 'Update already in progress' };
|
|
@@ -169,6 +180,7 @@ async function executeUpdate({
|
|
|
169
180
|
timeout: INSTALL_TIMEOUT_MS,
|
|
170
181
|
maxBuffer: 10 * 1024 * 1024, // 10 MB — package manager installs can be verbose
|
|
171
182
|
env: { ...process.env, NO_UPDATE_NOTIFIER: '1' },
|
|
183
|
+
cwd: cwd || undefined,
|
|
172
184
|
});
|
|
173
185
|
|
|
174
186
|
const output = (stdout + '\n' + stderr).trim();
|
|
@@ -178,7 +190,7 @@ async function executeUpdate({
|
|
|
178
190
|
// Step 3: Verify
|
|
179
191
|
notify({ status: 'verifying', phase: 'Verifying update...' });
|
|
180
192
|
|
|
181
|
-
const newVersion = await verifyInstalledVersion(method);
|
|
193
|
+
const newVersion = await verifyInstalledVersion(method, cwd);
|
|
182
194
|
if (!newVersion) {
|
|
183
195
|
notify({
|
|
184
196
|
status: 'failed',
|
|
@@ -230,7 +242,22 @@ async function executeUpdate({
|
|
|
230
242
|
|
|
231
243
|
// ── Version Verification ─────────────────────────────────────────────────────
|
|
232
244
|
|
|
233
|
-
async function verifyInstalledVersion(method) {
|
|
245
|
+
async function verifyInstalledVersion(method, cwd) {
|
|
246
|
+
// Source installs: read version from the repo's package.json after git pull
|
|
247
|
+
if (method === 'source') {
|
|
248
|
+
try {
|
|
249
|
+
const pkgPath = cwd
|
|
250
|
+
? path.join(cwd, 'package.json')
|
|
251
|
+
: path.resolve(__dirname, '../../package.json');
|
|
252
|
+
const content = await fs.promises.readFile(pkgPath, 'utf8');
|
|
253
|
+
const pkg = JSON.parse(content);
|
|
254
|
+
return pkg.version || null;
|
|
255
|
+
} catch (err) {
|
|
256
|
+
log.debug(`Version verification via package.json failed: ${err.message}`);
|
|
257
|
+
}
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
|
|
234
261
|
const cmd = method === 'yarn' ? 'yarn' : method === 'pnpm' ? 'pnpm' : 'npm';
|
|
235
262
|
try {
|
|
236
263
|
// Use npm/yarn/pnpm to read the installed version
|