fix(cli): warn about version mismatch between cli and daemon (#2461)
Co-authored-by: Piotr Paulski <piotrpaulski@chromium.org>
This commit is contained in:
@@ -17,7 +17,9 @@ import {
|
||||
stopDaemon,
|
||||
sendCommand,
|
||||
handleResponse,
|
||||
verifyDaemonVersion,
|
||||
} from '../daemon/client.js';
|
||||
import type {DaemonStatusResult} from '../daemon/types.js';
|
||||
import {isDaemonRunning, serializeArgs} from '../daemon/utils.js';
|
||||
import {logDisclaimers} from '../index.js';
|
||||
import {hideBin, yargs, type CallToolResult} from '../third_party/index.js';
|
||||
@@ -159,17 +161,16 @@ y.command(
|
||||
argv.sessionId,
|
||||
);
|
||||
if (response.success) {
|
||||
const data = JSON.parse(response.result) as {
|
||||
pid: number | null;
|
||||
socketPath: string;
|
||||
startDate: string;
|
||||
version: string;
|
||||
args: string[];
|
||||
};
|
||||
const data: DaemonStatusResult = JSON.parse(response.result);
|
||||
console.log(
|
||||
`pid=${data.pid} socket=${data.socketPath} start-date=${data.startDate} version=${data.version}`,
|
||||
);
|
||||
console.log(`args=${JSON.stringify(data.args)}`);
|
||||
if (data.version !== VERSION) {
|
||||
console.warn(
|
||||
`Warning: Daemon server version (${data.version}) does not match CLI version (${VERSION}). Run 'chrome-devtools start' to update and restart the daemon.`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.error('Error:', response.error);
|
||||
process.exit(1);
|
||||
@@ -262,6 +263,10 @@ for (const [commandName, commandDef] of Object.entries(commands)) {
|
||||
async argv => {
|
||||
const sessionId = argv.sessionId as string;
|
||||
try {
|
||||
const versionWarningPromise = isDaemonRunning(sessionId)
|
||||
? verifyDaemonVersion(sessionId, VERSION)
|
||||
: Promise.resolve(undefined);
|
||||
|
||||
if (!isDaemonRunning(sessionId)) {
|
||||
await start(serializeArgs(cliOptions, argv), sessionId);
|
||||
}
|
||||
@@ -291,6 +296,14 @@ for (const [commandName, commandDef] of Object.entries(commands)) {
|
||||
);
|
||||
} else {
|
||||
console.error('Error:', response.error);
|
||||
}
|
||||
|
||||
const versionWarning = await versionWarningPromise;
|
||||
if (versionWarning) {
|
||||
console.warn(versionWarning);
|
||||
}
|
||||
|
||||
if (!response.success) {
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
+26
-1
@@ -13,7 +13,11 @@ import {PipeTransport} from '../third_party/index.js';
|
||||
import {getTempFilePath} from '../utils/files.js';
|
||||
import {logger} from '../utils/logger.js';
|
||||
|
||||
import type {DaemonMessage, DaemonResponse} from './types.js';
|
||||
import type {
|
||||
DaemonMessage,
|
||||
DaemonResponse,
|
||||
DaemonStatusResult,
|
||||
} from './types.js';
|
||||
import {
|
||||
DAEMON_SCRIPT_PATH,
|
||||
getSocketPath,
|
||||
@@ -194,6 +198,27 @@ export async function stopDaemon(sessionId: string) {
|
||||
await waitForFile(pidFilePath, /*removed=*/ true);
|
||||
}
|
||||
|
||||
export async function verifyDaemonVersion(
|
||||
sessionId: string,
|
||||
cliVersion: string,
|
||||
): Promise<string | undefined> {
|
||||
if (!isDaemonRunning(sessionId)) {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
const response = await sendCommand({method: 'status'}, sessionId);
|
||||
if (response.success) {
|
||||
const data: DaemonStatusResult = JSON.parse(response.result);
|
||||
if (data?.version && data.version !== cliVersion) {
|
||||
return `Warning: Daemon server version (${data.version}) does not match CLI version (${cliVersion}). Run 'chrome-devtools start' to update and restart the daemon.`;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Suppress communication failures during check; command execution handles unreachable daemon errors.
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export async function handleResponse(
|
||||
response: CallToolResult,
|
||||
format: 'json' | 'md',
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
import {logger} from '../utils/logger.js';
|
||||
import {VERSION} from '../version.js';
|
||||
|
||||
import type {DaemonMessage} from './types.js';
|
||||
import type {DaemonMessage, DaemonStatusResult} from './types.js';
|
||||
import {
|
||||
DAEMON_CLIENT_NAME,
|
||||
getPidFilePath,
|
||||
@@ -186,15 +186,16 @@ async function handleRequest(msg: DaemonMessage) {
|
||||
};
|
||||
} else if (msg.method === 'status') {
|
||||
await started;
|
||||
const statusResult: DaemonStatusResult = {
|
||||
pid: process.pid,
|
||||
socketPath,
|
||||
startDate: startDate.toISOString(),
|
||||
version: VERSION,
|
||||
args: mcpServerArgs,
|
||||
};
|
||||
return {
|
||||
success: true,
|
||||
result: JSON.stringify({
|
||||
pid: process.pid,
|
||||
socketPath,
|
||||
startDate: startDate.toISOString(),
|
||||
version: VERSION,
|
||||
args: mcpServerArgs,
|
||||
}),
|
||||
result: JSON.stringify(statusResult),
|
||||
};
|
||||
}
|
||||
{
|
||||
|
||||
@@ -23,3 +23,11 @@ export interface DaemonResponse {
|
||||
result: string;
|
||||
error: unknown;
|
||||
}
|
||||
|
||||
export interface DaemonStatusResult {
|
||||
pid: number | null;
|
||||
socketPath: string;
|
||||
startDate: string;
|
||||
version: string;
|
||||
args: string[];
|
||||
}
|
||||
|
||||
+44
-11
@@ -14,22 +14,24 @@ import {
|
||||
handleResponse,
|
||||
startDaemon,
|
||||
stopDaemon,
|
||||
verifyDaemonVersion,
|
||||
} from '../../src/daemon/client.js';
|
||||
import {isDaemonRunning} from '../../src/daemon/utils.js';
|
||||
import {VERSION} from '../../src/version.js';
|
||||
|
||||
describe('daemon client', () => {
|
||||
let sessionId: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
sessionId = crypto.randomUUID();
|
||||
await stopDaemon(sessionId);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await stopDaemon(sessionId);
|
||||
});
|
||||
|
||||
describe('start/stop', () => {
|
||||
let sessionId: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
sessionId = crypto.randomUUID();
|
||||
await stopDaemon(sessionId);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await stopDaemon(sessionId);
|
||||
});
|
||||
|
||||
it('should start and stop daemon', async () => {
|
||||
assert.ok(
|
||||
!isDaemonRunning(sessionId),
|
||||
@@ -73,6 +75,37 @@ describe('daemon client', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('verifyDaemonVersion', () => {
|
||||
it('warns when daemon version does not match CLI version', async () => {
|
||||
await startDaemon([], sessionId);
|
||||
const warning = await verifyDaemonVersion(sessionId, '0.0.0-mismatch');
|
||||
assert.ok(
|
||||
warning &&
|
||||
warning.includes('does not match CLI version (0.0.0-mismatch)'),
|
||||
`Expected warning message about version mismatch, got: ${warning}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('does not warn when daemon version matches CLI version', async () => {
|
||||
await startDaemon([], sessionId);
|
||||
const warning = await verifyDaemonVersion(sessionId, VERSION);
|
||||
assert.strictEqual(
|
||||
warning,
|
||||
undefined,
|
||||
'Should not return warning when version matches',
|
||||
);
|
||||
});
|
||||
|
||||
it('does nothing when daemon is not running', async () => {
|
||||
const warning = await verifyDaemonVersion(sessionId, '0.0.0-mismatch');
|
||||
assert.strictEqual(
|
||||
warning,
|
||||
undefined,
|
||||
'Should not return warning when daemon is stopped',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parsing', () => {
|
||||
it('handles MCP response with text format', async () => {
|
||||
const textResponse = {content: [{type: 'text' as const, text: 'test'}]};
|
||||
|
||||
Reference in New Issue
Block a user