diff --git a/scripts/cursor-hooks.js b/scripts/cursor-hooks.js index 8d222bd5e..1ccc8f6d0 100644 --- a/scripts/cursor-hooks.js +++ b/scripts/cursor-hooks.js @@ -36,8 +36,18 @@ function readConfig(file) { } catch (e) { if (e.code !== 'ENOENT') throw e; } - if (!config || typeof config !== 'object' || Array.isArray(config)) config = {}; - if (!config.hooks || typeof config.hooks !== 'object' || Array.isArray(config.hooks)) config.hooks = {}; + if (!config || typeof config !== 'object' || Array.isArray(config)) { + throw new Error(file + ' must contain a JSON object; nothing was changed'); + } + if (config.hooks === undefined) config.hooks = {}; + if (!config.hooks || typeof config.hooks !== 'object' || Array.isArray(config.hooks)) { + throw new Error(file + ': hooks must be an object; nothing was changed'); + } + for (const [event, entries] of Object.entries(config.hooks)) { + if (!Array.isArray(entries)) { + throw new Error(file + ': hooks.' + event + ' must be an array; nothing was changed'); + } + } return config; } diff --git a/tests/cursor-hooks.test.js b/tests/cursor-hooks.test.js index a604c817b..09f438cc5 100644 --- a/tests/cursor-hooks.test.js +++ b/tests/cursor-hooks.test.js @@ -315,3 +315,20 @@ test('installer refuses to touch a malformed hooks.json', () => { assert.equal(fs.readFileSync(file, 'utf8'), broken, 'malformed file must be left byte-for-byte intact'); } }); + +for (const [name, config] of Object.entries({ + 'root-array': [{ command: 'keep me' }], + 'hooks-array': { hooks: [{ command: 'keep me' }] }, + 'event-string': { hooks: { sessionStart: 'node custom-hook.js' } }, +})) { + test(`installer refuses ${name} without overwriting hooks.json`, () => { + const c = cursorEnv(`invalid-${name}`); + const file = path.join(c.home, '.cursor', 'hooks.json'); + fs.mkdirSync(path.dirname(file), { recursive: true }); + const original = JSON.stringify(config, null, 2) + '\n'; + fs.writeFileSync(file, original); + const result = cli(['install'], c.env); + assert.notEqual(result.status, 0, 'invalid config must not be silently rewritten'); + assert.equal(fs.readFileSync(file, 'utf8'), original); + }); +}