mirror of
https://github.com/thedotmack/claude-mem.git
synced 2026-09-20 04:23:02 +08:00
fix(windsurf): read hooks.json with the BOM-tolerant parser (#4010)
Night-ship rebase of #3670. Route the three Windsurf hooks.json reads through existing parseJsonWithBom so a UTF-8 BOM no longer looks like a corrupt file. Refs #3670
This commit is contained in:
@@ -4,6 +4,7 @@ import { homedir } from 'os';
|
||||
import { existsSync, readFileSync, writeFileSync, unlinkSync, mkdirSync, renameSync } from 'fs';
|
||||
import { logger } from '../../utils/logger.js';
|
||||
import { getWorkerHost, getWorkerPort } from '../../shared/worker-utils.js';
|
||||
import { parseJsonWithBom } from '../../shared/atomic-json.js';
|
||||
import { DATA_DIR } from '../../shared/paths.js';
|
||||
import { getBunAbsolutePath as findBunPath, getWorkerServiceAbsolutePath as findWorkerServicePath } from './install-paths.js';
|
||||
|
||||
@@ -129,7 +130,7 @@ function mergeAndWriteHooksJson(
|
||||
let existingConfig: WindsurfHooksJson = { hooks: {} };
|
||||
if (existsSync(WINDSURF_HOOKS_JSON_PATH)) {
|
||||
try {
|
||||
existingConfig = JSON.parse(readFileSync(WINDSURF_HOOKS_JSON_PATH, 'utf-8'));
|
||||
existingConfig = parseJsonWithBom<WindsurfHooksJson>(readFileSync(WINDSURF_HOOKS_JSON_PATH, 'utf-8'));
|
||||
if (!existingConfig.hooks) {
|
||||
existingConfig.hooks = {};
|
||||
}
|
||||
@@ -316,7 +317,7 @@ export function uninstallWindsurfHooks(): number {
|
||||
}
|
||||
|
||||
function removeClaudeMemHookEntries(): void {
|
||||
const parsed = JSON.parse(readFileSync(WINDSURF_HOOKS_JSON_PATH, 'utf-8')) as Partial<WindsurfHooksJson>;
|
||||
const parsed = parseJsonWithBom<Partial<WindsurfHooksJson>>(readFileSync(WINDSURF_HOOKS_JSON_PATH, 'utf-8'));
|
||||
const config: WindsurfHooksJson = { hooks: parsed.hooks ?? {} };
|
||||
|
||||
for (const eventName of WINDSURF_HOOK_EVENTS) {
|
||||
@@ -363,7 +364,7 @@ export function checkWindsurfHooksStatus(): number {
|
||||
|
||||
let parsedConfig: Partial<WindsurfHooksJson> | null = null;
|
||||
try {
|
||||
parsedConfig = JSON.parse(readFileSync(WINDSURF_HOOKS_JSON_PATH, 'utf-8'));
|
||||
parsedConfig = parseJsonWithBom(readFileSync(WINDSURF_HOOKS_JSON_PATH, 'utf-8'));
|
||||
} catch (error) {
|
||||
const normalizedError = error instanceof Error ? error : new Error(String(error));
|
||||
logger.error('WORKER', 'Unable to parse hooks.json', { path: WINDSURF_HOOKS_JSON_PATH }, normalizedError);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { describe, it, expect } from 'bun:test';
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { parseJsonWithBom } from '../src/shared/atomic-json.js';
|
||||
|
||||
// The installer writes to a fixed path under the real home directory, so a filesystem test would
|
||||
// touch the developer's own Windsurf config. This asserts against the source instead, the same way
|
||||
// npm-install-windows-hide.test.ts does for its spawn options.
|
||||
const SOURCE = readFileSync(
|
||||
join(import.meta.dir, '../src/services/integrations/WindsurfHooksInstaller.ts'),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
describe("WindsurfHooksInstaller reads Windsurf's hooks.json", () => {
|
||||
it('never parses that file with a BOM-blind JSON.parse', () => {
|
||||
// A hooks.json rewritten by PowerShell 5.1 or a Windows editor carries a UTF-8 BOM. Parsing it
|
||||
// with JSON.parse throws, and the catch turns a perfectly valid file into "Corrupt hooks.json,
|
||||
// refusing to overwrite" — so install and uninstall both stop on a file that is not corrupt.
|
||||
expect(SOURCE).not.toMatch(/JSON\.parse\(\s*readFileSync\(\s*WINDSURF_HOOKS_JSON_PATH/);
|
||||
});
|
||||
|
||||
it('uses the shared BOM-tolerant reader for each of the three reads', () => {
|
||||
// `[^(]*` rather than `<[^>]*>` so a nested type argument such as
|
||||
// `<Partial<WindsurfHooksJson>>` still counts.
|
||||
const uses = SOURCE.match(
|
||||
/parseJsonWithBom[^(]*\(\s*readFileSync\(\s*WINDSURF_HOOKS_JSON_PATH/g,
|
||||
);
|
||||
expect(uses?.length).toBe(3);
|
||||
});
|
||||
|
||||
it('that reader accepts what JSON.parse rejects', () => {
|
||||
const bommed = '\uFEFF' + JSON.stringify({ hooks: { afterFileEdit: [] } });
|
||||
expect(() => JSON.parse(bommed)).toThrow();
|
||||
expect(parseJsonWithBom<{ hooks: Record<string, unknown> }>(bommed).hooks).toBeDefined();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user