@@ -5,6 +5,21 @@ const path = require('path');
55
66const { writeInstallState } = require ( '../install-state' ) ;
77
8+ function readJsonObject ( filePath , label ) {
9+ let parsed ;
10+ try {
11+ parsed = JSON . parse ( fs . readFileSync ( filePath , 'utf8' ) ) ;
12+ } catch ( error ) {
13+ throw new Error ( `Failed to parse ${ label } at ${ filePath } : ${ error . message } ` ) ;
14+ }
15+
16+ if ( ! parsed || typeof parsed !== 'object' || Array . isArray ( parsed ) ) {
17+ throw new Error ( `Invalid ${ label } at ${ filePath } : expected a JSON object` ) ;
18+ }
19+
20+ return parsed ;
21+ }
22+
823function mergeHookEntries ( existingEntries , incomingEntries ) {
924 const mergedEntries = [ ] ;
1025 const seenEntries = new Set ( ) ;
@@ -22,39 +37,32 @@ function mergeHookEntries(existingEntries, incomingEntries) {
2237 return mergedEntries ;
2338}
2439
25- function mergeHooksIntoSettings ( plan ) {
26- if ( ! plan . adapter || plan . adapter . target !== 'claude' ) {
27- return ;
28- }
40+ function findHooksSourcePath ( plan , hooksDestinationPath ) {
41+ const operation = plan . operations . find ( item => item . destinationPath === hooksDestinationPath ) ;
42+ return operation ? operation . sourcePath : null ;
43+ }
2944
30- const hooksJsonPath = path . join ( plan . targetRoot , 'hooks' , 'hooks.json' ) ;
31- if ( ! fs . existsSync ( hooksJsonPath ) ) {
32- return ;
45+ function buildMergedSettings ( plan ) {
46+ if ( ! plan . adapter || plan . adapter . target !== 'claude' ) {
47+ return null ;
3348 }
3449
35- let hooksConfig ;
36- try {
37- hooksConfig = JSON . parse ( fs . readFileSync ( hooksJsonPath , 'utf8' ) ) ;
38- } catch ( error ) {
39- throw new Error ( `Failed to parse hooks config at ${ hooksJsonPath } : ${ error . message } ` ) ;
50+ const hooksDestinationPath = path . join ( plan . targetRoot , 'hooks' , 'hooks.json' ) ;
51+ const hooksSourcePath = findHooksSourcePath ( plan , hooksDestinationPath ) || hooksDestinationPath ;
52+ if ( ! fs . existsSync ( hooksSourcePath ) ) {
53+ return null ;
4054 }
4155
56+ const hooksConfig = readJsonObject ( hooksSourcePath , 'hooks config' ) ;
4257 const incomingHooks = hooksConfig . hooks ;
4358 if ( ! incomingHooks || typeof incomingHooks !== 'object' || Array . isArray ( incomingHooks ) ) {
44- return ;
59+ throw new Error ( `Invalid hooks config at ${ hooksSourcePath } : expected "hooks" to be a JSON object` ) ;
4560 }
4661
4762 const settingsPath = path . join ( plan . targetRoot , 'settings.json' ) ;
4863 let settings = { } ;
4964 if ( fs . existsSync ( settingsPath ) ) {
50- try {
51- settings = JSON . parse ( fs . readFileSync ( settingsPath , 'utf8' ) ) ;
52- if ( ! settings || typeof settings !== 'object' || Array . isArray ( settings ) ) {
53- throw new Error ( 'root value must be a JSON object' ) ;
54- }
55- } catch ( error ) {
56- throw new Error ( `Failed to parse existing settings at ${ settingsPath } : ${ error . message } ` ) ;
57- }
65+ settings = readJsonObject ( settingsPath , 'existing settings' ) ;
5866 }
5967
6068 const existingHooks = settings . hooks && typeof settings . hooks === 'object' && ! Array . isArray ( settings . hooks )
@@ -73,17 +81,29 @@ function mergeHooksIntoSettings(plan) {
7381 hooks : mergedHooks ,
7482 } ;
7583
76- fs . mkdirSync ( path . dirname ( settingsPath ) , { recursive : true } ) ;
77- fs . writeFileSync ( settingsPath , JSON . stringify ( mergedSettings , null , 2 ) + '\n' , 'utf8' ) ;
84+ return {
85+ settingsPath,
86+ mergedSettings,
87+ } ;
7888}
7989
8090function applyInstallPlan ( plan ) {
91+ const mergedSettingsPlan = buildMergedSettings ( plan ) ;
92+
8193 for ( const operation of plan . operations ) {
8294 fs . mkdirSync ( path . dirname ( operation . destinationPath ) , { recursive : true } ) ;
8395 fs . copyFileSync ( operation . sourcePath , operation . destinationPath ) ;
8496 }
8597
86- mergeHooksIntoSettings ( plan ) ;
98+ if ( mergedSettingsPlan ) {
99+ fs . mkdirSync ( path . dirname ( mergedSettingsPlan . settingsPath ) , { recursive : true } ) ;
100+ fs . writeFileSync (
101+ mergedSettingsPlan . settingsPath ,
102+ JSON . stringify ( mergedSettingsPlan . mergedSettings , null , 2 ) + '\n' ,
103+ 'utf8'
104+ ) ;
105+ }
106+
87107 writeInstallState ( plan . installStatePath , plan . statePreview ) ;
88108
89109 return {
0 commit comments