项目文件夹

文件
Rayan Salhab 6225ffbaf1 fix: hide Windows update check consoles (#2231)
## Summary

Fixes #2230.

- Sets `windowsHide: true` on the detached update-check process so
Windows does not flash a console window.
- Also hides the `npm config get registry` subprocess used by the
updater helper.
- Covers the update-check spawn options in the existing tests.

## Verification

- `NODE_OPTIONS=--max-old-space-size=4096 npm run build`
- `npm run test:no-build -- tests/check-for-updates.test.ts`
- `NODE_OPTIONS=--max-old-space-size=4096 npx eslint
src/utils/check-for-updates.ts src/bin/check-latest-version.ts
tests/check-for-updates.test.ts`
- `npx prettier --check src/utils/check-for-updates.ts
src/bin/check-latest-version.ts tests/check-for-updates.test.ts`
- `git diff --check`

Note: plain `npm run build` and full-repo `npm run check-format` hit the
local Node heap limit in this runner; the same build passed with the
heap limit raised, and touched-file lint/format checks passed.

Co-authored-by: cyphercodes <cyphercodes@users.noreply.github.com>
2026-06-19 05:31:01 +00:00

171 行
5.6 KiB
TypeScript

/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import assert from 'node:assert';
import child_process from 'node:child_process';
import type {Stats} from 'node:fs';
import fs from 'node:fs/promises';
import os from 'node:os';
import {afterEach, beforeEach, describe, it} from 'node:test';
import sinon from 'sinon';
import {
checkForUpdates,
resetUpdateCheckFlagForTesting,
} from '../src/utils/check-for-updates.js';
import {VERSION} from '../src/version.js';
describe('checkForUpdates', () => {
beforeEach(() => {
sinon.stub(fs, 'mkdir').resolves();
sinon.stub(fs, 'utimes').resolves();
sinon.stub(fs, 'writeFile').resolves();
});
afterEach(() => {
sinon.restore();
resetUpdateCheckFlagForTesting();
});
it('does nothing if CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS is set', async () => {
process.env['CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS'] = 'true';
const warnStub = sinon.stub(console, 'warn');
const spawnStub = sinon.stub(child_process, 'spawn');
const readFileStub = sinon.stub(fs, 'readFile');
const statStub = sinon.stub(fs, 'stat');
await checkForUpdates('Run `npm update` to update.');
assert.ok(warnStub.notCalled);
assert.ok(spawnStub.notCalled);
assert.ok(readFileStub.notCalled);
assert.ok(statStub.notCalled);
delete process.env['CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS'];
});
it('notifies if cache exists and version is different', async () => {
sinon.stub(os, 'homedir').returns('/home/user');
sinon.stub(fs, 'stat').resolves({mtimeMs: Date.now()} as unknown as Stats);
sinon.stub(fs, 'readFile').callsFake(async filePath => {
if (filePath.toString().includes('latest.json')) {
return JSON.stringify({
version: '99.9.9',
});
}
throw new Error(`File not found: ${filePath}`);
});
const warnStub = sinon.stub(console, 'warn');
const spawnStub = sinon.stub(child_process, 'spawn');
await checkForUpdates('Run `npm update` to update.');
assert.ok(
warnStub.calledWith(
sinon.match('Update available: ' + VERSION + ' -> 99.9.9'),
),
);
assert.ok(spawnStub.notCalled);
});
it('does not notify if incoming version is older than current version', async () => {
sinon.stub(os, 'homedir').returns('/home/user');
sinon.stub(fs, 'stat').resolves({mtimeMs: Date.now()} as unknown as Stats);
sinon.stub(fs, 'readFile').callsFake(async filePath => {
if (filePath.toString().includes('latest.json')) {
return JSON.stringify({
version: '0.0.1',
});
}
throw new Error(`File not found: ${filePath}`);
});
const warnStub = sinon.stub(console, 'warn');
const spawnStub = sinon.stub(child_process, 'spawn');
await checkForUpdates('Run `npm update` to update.');
assert.ok(warnStub.notCalled);
assert.ok(spawnStub.notCalled);
});
it('does not spawn fetch process if cache is fresh', async () => {
sinon.stub(os, 'homedir').returns('/home/user');
sinon.stub(fs, 'stat').resolves({mtimeMs: Date.now()} as unknown as Stats);
sinon.stub(fs, 'readFile').callsFake(async filePath => {
if (filePath.toString().includes('latest.json')) {
return JSON.stringify({
version: VERSION,
});
}
throw new Error(`File not found: ${filePath}`);
});
const spawnStub = sinon.stub(child_process, 'spawn');
await checkForUpdates('Run `npm update` to update.');
assert.ok(spawnStub.notCalled);
});
it('spawns detached process if cache is stale', async () => {
sinon.stub(os, 'homedir').returns('/home/user');
sinon.stub(fs, 'stat').resolves({
mtimeMs: Date.now() - 25 * 60 * 60 * 1000, // 25 hours ago
} as unknown as Stats);
sinon.stub(fs, 'readFile').callsFake(async filePath => {
if (filePath.toString().includes('latest.json')) {
return JSON.stringify({
version: VERSION,
});
}
throw new Error(`File not found: ${filePath}`);
});
const unrefSpy = sinon.spy();
const spawnStub = sinon.stub(child_process, 'spawn').returns({
unref: unrefSpy,
} as unknown as child_process.ChildProcess);
await checkForUpdates('Run `npm update` to update.');
assert.ok(spawnStub.calledOnce);
assert.strictEqual(spawnStub.firstCall.args[0], process.execPath);
assert.ok(
spawnStub.firstCall.args[1][0]?.includes('check-latest-version.js'),
);
assert.ok(spawnStub.firstCall.args[1][1]?.includes('latest.json'));
assert.strictEqual(spawnStub.firstCall.args[2]?.detached, true);
assert.strictEqual(spawnStub.firstCall.args[2]?.windowsHide, true);
assert.ok(unrefSpy.calledOnce);
});
it('spawns detached process if cache is missing', async () => {
sinon.stub(os, 'homedir').returns('/home/user');
sinon.stub(fs, 'stat').rejects(new Error('File not found'));
sinon.stub(fs, 'readFile').callsFake(async filePath => {
throw new Error(`File not found: ${filePath}`);
});
const unrefSpy = sinon.spy();
const spawnStub = sinon.stub(child_process, 'spawn').returns({
unref: unrefSpy,
} as unknown as child_process.ChildProcess);
await checkForUpdates('Run `npm update` to update.');
assert.ok(spawnStub.calledOnce);
assert.strictEqual(spawnStub.firstCall.args[0], process.execPath);
assert.ok(
spawnStub.firstCall.args[1][0]?.includes('check-latest-version.js'),
);
assert.ok(spawnStub.firstCall.args[1][1]?.includes('latest.json'));
assert.strictEqual(spawnStub.firstCall.args[2]?.detached, true);
assert.strictEqual(spawnStub.firstCall.args[2]?.windowsHide, true);
assert.ok(unrefSpy.calledOnce);
});
});