mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
refactor(daemon): use mtime-based version detection instead of string comparison
Replace CLI version string comparison with file modification time (mtime) based detection. This provides a more reliable way to detect when the CLI binary has been updated, especially for bun-compiled executables where package.json may not be accessible. Changes: - Add getInstalledCliMtimeMs() utility to check CLI binary or package.json mtime - Store startedWithCliMtimeMs in daemon state on startup - Use mtime comparison in version check loop for daemon auto-restart - Move version flag handling to early CLI startup before daemon initialization - Fix machine active state merging to prefer newer activeAt timestamp This improves daemon restart behavior when CLI is updated via package managers.
This commit is contained in:
@@ -6,10 +6,31 @@
|
||||
import { logger } from '@/ui/logger';
|
||||
import { clearDaemonState, readDaemonState } from '@/persistence';
|
||||
import { Metadata } from '@/api/types';
|
||||
import { projectPath } from '@/projectPath';
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { configuration } from '@/configuration';
|
||||
import packageJson from '../../package.json';
|
||||
import { existsSync, statSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { isBunCompiled, projectPath } from '@/projectPath';
|
||||
|
||||
export function getInstalledCliMtimeMs(): number | undefined {
|
||||
if (isBunCompiled()) {
|
||||
try {
|
||||
return statSync(process.execPath).mtimeMs;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const packageJsonPath = join(projectPath(), 'package.json');
|
||||
if (!existsSync(packageJsonPath)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
return statSync(packageJsonPath).mtimeMs;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async function daemonPost(path: string, body?: any): Promise<{ error?: string } | any> {
|
||||
const state = await readDaemonState();
|
||||
@@ -154,11 +175,13 @@ export async function isDaemonRunningCurrentlyInstalledHappyVersion(): Promise<b
|
||||
}
|
||||
|
||||
try {
|
||||
// Read package.json on demand from disk - so we are guaranteed to get the latest version
|
||||
const packageJsonPath = join(projectPath(), 'package.json');
|
||||
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
||||
const currentCliMtimeMs = getInstalledCliMtimeMs();
|
||||
if (typeof currentCliMtimeMs === 'number' && typeof state.startedWithCliMtimeMs === 'number') {
|
||||
logger.debug(`[DAEMON CONTROL] Current CLI mtime: ${currentCliMtimeMs}, Daemon started with mtime: ${state.startedWithCliMtimeMs}`);
|
||||
return currentCliMtimeMs === state.startedWithCliMtimeMs;
|
||||
}
|
||||
|
||||
const currentCliVersion = packageJson.version;
|
||||
|
||||
logger.debug(`[DAEMON CONTROL] Current CLI version: ${currentCliVersion}, Daemon started with version: ${state.startedWithCliVersion}`);
|
||||
return currentCliVersion === state.startedWithCliVersion;
|
||||
|
||||
|
||||
+10
-7
@@ -13,11 +13,10 @@ import { getEnvironmentInfo } from '@/ui/doctor';
|
||||
import { spawnHappyCLI } from '@/utils/spawnHappyCLI';
|
||||
import { writeDaemonState, DaemonLocallyPersistedState, readDaemonState, acquireDaemonLock, releaseDaemonLock } from '@/persistence';
|
||||
|
||||
import { cleanupDaemonState, isDaemonRunningCurrentlyInstalledHappyVersion, stopDaemon } from './controlClient';
|
||||
import { cleanupDaemonState, getInstalledCliMtimeMs, isDaemonRunningCurrentlyInstalledHappyVersion, stopDaemon } from './controlClient';
|
||||
import { startDaemonControlServer } from './controlServer';
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { projectPath, runtimePath } from '@/projectPath';
|
||||
import { runtimePath } from '@/projectPath';
|
||||
|
||||
// Prepare initial metadata
|
||||
export const initialMachineMetadata: MachineMetadata = {
|
||||
@@ -403,12 +402,15 @@ export async function startDaemon(): Promise<void> {
|
||||
onHappySessionWebhook
|
||||
});
|
||||
|
||||
const startedWithCliMtimeMs = getInstalledCliMtimeMs();
|
||||
|
||||
// Write initial daemon state (no lock needed for state file)
|
||||
const fileState: DaemonLocallyPersistedState = {
|
||||
pid: process.pid,
|
||||
httpPort: controlPort,
|
||||
startTime: new Date().toLocaleString(),
|
||||
startedWithCliVersion: packageJson.version,
|
||||
startedWithCliMtimeMs,
|
||||
daemonLogPath: logger.logFilePath
|
||||
};
|
||||
writeDaemonState(fileState);
|
||||
@@ -476,10 +478,10 @@ export async function startDaemon(): Promise<void> {
|
||||
}
|
||||
|
||||
// Check if daemon needs update
|
||||
// If version on disk is different from the one in package.json - we need to restart
|
||||
// BIG if - does this get updated from underneath us on npm upgrade?
|
||||
const projectVersion = JSON.parse(readFileSync(join(projectPath(), 'package.json'), 'utf-8')).version;
|
||||
if (projectVersion !== configuration.currentCliVersion) {
|
||||
const installedCliMtimeMs = getInstalledCliMtimeMs();
|
||||
if (typeof installedCliMtimeMs === 'number' &&
|
||||
typeof startedWithCliMtimeMs === 'number' &&
|
||||
installedCliMtimeMs !== startedWithCliMtimeMs) {
|
||||
logger.debug('[DAEMON RUN] Daemon is outdated, triggering self-restart with latest version, clearing heartbeat interval');
|
||||
|
||||
clearInterval(restartOnStaleVersionAndHeartbeat);
|
||||
@@ -521,6 +523,7 @@ export async function startDaemon(): Promise<void> {
|
||||
httpPort: controlPort,
|
||||
startTime: fileState.startTime,
|
||||
startedWithCliVersion: packageJson.version,
|
||||
startedWithCliMtimeMs,
|
||||
lastHeartbeat: new Date().toLocaleString(),
|
||||
daemonLogPath: fileState.daemonLogPath
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user