deepflow 0.1.106 → 0.1.107
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/install.js +26 -11
- package/bin/install.test.js +93 -167
- package/package.json +1 -1
package/bin/install.js
CHANGED
|
@@ -156,14 +156,21 @@ async function main() {
|
|
|
156
156
|
if (level === 'global') {
|
|
157
157
|
const hooksDir = path.join(PACKAGE_DIR, 'hooks');
|
|
158
158
|
if (fs.existsSync(hooksDir)) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
path.join(
|
|
163
|
-
|
|
164
|
-
|
|
159
|
+
const copyDirRecursive = (srcDir, destDir) => {
|
|
160
|
+
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
161
|
+
if (entry.isDirectory()) {
|
|
162
|
+
const subDest = path.join(destDir, entry.name);
|
|
163
|
+
fs.mkdirSync(subDest, { recursive: true });
|
|
164
|
+
copyDirRecursive(path.join(srcDir, entry.name), subDest);
|
|
165
|
+
} else if (entry.name.endsWith('.js')) {
|
|
166
|
+
fs.copyFileSync(
|
|
167
|
+
path.join(srcDir, entry.name),
|
|
168
|
+
path.join(destDir, entry.name)
|
|
169
|
+
);
|
|
170
|
+
}
|
|
165
171
|
}
|
|
166
|
-
}
|
|
172
|
+
};
|
|
173
|
+
copyDirRecursive(hooksDir, path.join(CLAUDE_DIR, 'hooks'));
|
|
167
174
|
log('Hooks installed');
|
|
168
175
|
}
|
|
169
176
|
}
|
|
@@ -620,6 +627,8 @@ async function uninstall() {
|
|
|
620
627
|
}
|
|
621
628
|
}
|
|
622
629
|
}
|
|
630
|
+
// Remove hooks/lib (shared hook utilities)
|
|
631
|
+
toRemove.push('hooks/lib');
|
|
623
632
|
}
|
|
624
633
|
|
|
625
634
|
for (const item of toRemove) {
|
|
@@ -707,7 +716,13 @@ async function uninstall() {
|
|
|
707
716
|
console.log('');
|
|
708
717
|
}
|
|
709
718
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
719
|
+
// Export for testing
|
|
720
|
+
module.exports = { scanHookEvents, removeDeepflowHooks };
|
|
721
|
+
|
|
722
|
+
// Only run main when executed directly (not when required by tests)
|
|
723
|
+
if (require.main === module) {
|
|
724
|
+
main().catch(err => {
|
|
725
|
+
console.error('Installation failed:', err.message);
|
|
726
|
+
process.exit(1);
|
|
727
|
+
});
|
|
728
|
+
}
|
package/bin/install.test.js
CHANGED
|
@@ -590,217 +590,143 @@ describe('Uninstaller — file removal and settings cleanup', () => {
|
|
|
590
590
|
});
|
|
591
591
|
|
|
592
592
|
// ---------------------------------------------------------------------------
|
|
593
|
-
// T4. command-usage hook registration
|
|
593
|
+
// T4. command-usage hook registration via dynamic @hook-event tags
|
|
594
594
|
// ---------------------------------------------------------------------------
|
|
595
595
|
|
|
596
596
|
describe('T4 — command-usage hook registration in install.js', () => {
|
|
597
597
|
|
|
598
|
-
// --
|
|
598
|
+
// -- @hook-event tag: verify df-command-usage.js declares correct events --
|
|
599
599
|
|
|
600
|
-
test('
|
|
601
|
-
const
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
);
|
|
600
|
+
test('df-command-usage.js has @hook-event tag for PreToolUse, PostToolUse, SessionEnd', () => {
|
|
601
|
+
const hookPath = path.resolve(__dirname, '..', 'hooks', 'df-command-usage.js');
|
|
602
|
+
const content = fs.readFileSync(hookPath, 'utf8');
|
|
603
|
+
const firstLines = content.split('\n').slice(0, 10).join('\n');
|
|
604
|
+
const match = firstLines.match(/\/\/\s*@hook-event:\s*(.+)/);
|
|
605
|
+
assert.ok(match, 'df-command-usage.js should have @hook-event tag in first 10 lines');
|
|
606
|
+
const events = match[1].split(',').map(e => e.trim());
|
|
607
|
+
assert.ok(events.includes('PreToolUse'), 'Should declare PreToolUse event');
|
|
608
|
+
assert.ok(events.includes('PostToolUse'), 'Should declare PostToolUse event');
|
|
609
|
+
assert.ok(events.includes('SessionEnd'), 'Should declare SessionEnd event');
|
|
607
610
|
});
|
|
608
611
|
|
|
609
|
-
|
|
610
|
-
const src = fs.readFileSync(path.resolve(__dirname, 'install.js'), 'utf8');
|
|
611
|
-
// Find PreToolUse section — should contain a push with commandUsageCmd
|
|
612
|
-
const preToolUseSection = src.match(/PreToolUse[\s\S]*?log\('PreToolUse hook configured'\)/);
|
|
613
|
-
assert.ok(preToolUseSection, 'Should have a PreToolUse configuration section');
|
|
614
|
-
assert.ok(
|
|
615
|
-
preToolUseSection[0].includes('commandUsageCmd'),
|
|
616
|
-
'PreToolUse section should push commandUsageCmd'
|
|
617
|
-
);
|
|
618
|
-
});
|
|
612
|
+
// -- scanHookEvents: verify dynamic hook scanning maps events correctly --
|
|
619
613
|
|
|
620
|
-
test('
|
|
621
|
-
const
|
|
622
|
-
|
|
623
|
-
const
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
614
|
+
test('scanHookEvents maps df-command-usage.js to all three events', () => {
|
|
615
|
+
const { scanHookEvents } = require('./install.js');
|
|
616
|
+
const hooksDir = path.resolve(__dirname, '..', 'hooks');
|
|
617
|
+
const { eventMap } = scanHookEvents(hooksDir);
|
|
618
|
+
for (const event of ['PreToolUse', 'PostToolUse', 'SessionEnd']) {
|
|
619
|
+
assert.ok(eventMap.has(event), `eventMap should have ${event}`);
|
|
620
|
+
assert.ok(
|
|
621
|
+
eventMap.get(event).includes('df-command-usage.js'),
|
|
622
|
+
`${event} should include df-command-usage.js`
|
|
623
|
+
);
|
|
624
|
+
}
|
|
629
625
|
});
|
|
630
626
|
|
|
631
|
-
|
|
627
|
+
// -- configureHooks uses dynamic wiring (no hardcoded per-hook variables) --
|
|
628
|
+
|
|
629
|
+
test('source uses scanHookEvents for dynamic hook wiring', () => {
|
|
632
630
|
const src = fs.readFileSync(path.resolve(__dirname, 'install.js'), 'utf8');
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
assert.ok(sessionEndSection, 'Should have a SessionEnd configuration section');
|
|
636
|
-
assert.ok(
|
|
637
|
-
sessionEndSection[0].includes('commandUsageCmd'),
|
|
638
|
-
'SessionEnd section should push commandUsageCmd'
|
|
639
|
-
);
|
|
631
|
+
assert.ok(src.includes('scanHookEvents('), 'Should call scanHookEvents');
|
|
632
|
+
assert.ok(src.includes('for (const [event, files] of eventMap)'), 'Should iterate eventMap to wire hooks');
|
|
640
633
|
});
|
|
641
634
|
|
|
642
|
-
test('source
|
|
635
|
+
test('source initializes event array if missing', () => {
|
|
643
636
|
const src = fs.readFileSync(path.resolve(__dirname, 'install.js'), 'utf8');
|
|
644
637
|
assert.ok(
|
|
645
|
-
src.includes(
|
|
646
|
-
'
|
|
638
|
+
src.includes('if (!settings.hooks[event]) settings.hooks[event] = [];'),
|
|
639
|
+
'Should initialize event array dynamically'
|
|
647
640
|
);
|
|
648
641
|
});
|
|
649
642
|
|
|
650
|
-
// --
|
|
651
|
-
|
|
652
|
-
test('PreToolUse dedup filter removes existing df-command-usage entries', () => {
|
|
653
|
-
const preToolUse = [
|
|
654
|
-
{ hooks: [{ type: 'command', command: 'node /home/.claude/hooks/df-command-usage.js' }] },
|
|
655
|
-
{ hooks: [{ type: 'command', command: 'node /usr/local/my-custom.js' }] },
|
|
656
|
-
];
|
|
657
|
-
|
|
658
|
-
const filtered = preToolUse.filter(hook => {
|
|
659
|
-
const cmd = hook.hooks?.[0]?.command || '';
|
|
660
|
-
return !cmd.includes('df-command-usage');
|
|
661
|
-
});
|
|
643
|
+
// -- removeDeepflowHooks: generic removal of all /hooks/df- entries --
|
|
662
644
|
|
|
663
|
-
|
|
664
|
-
|
|
645
|
+
test('removeDeepflowHooks removes df-command-usage from all events', () => {
|
|
646
|
+
const { removeDeepflowHooks } = require('./install.js');
|
|
647
|
+
const settings = {
|
|
648
|
+
hooks: {
|
|
649
|
+
PreToolUse: [
|
|
650
|
+
{ hooks: [{ type: 'command', command: 'node /home/.claude/hooks/df-command-usage.js' }] },
|
|
651
|
+
{ hooks: [{ type: 'command', command: 'node /usr/local/my-custom.js' }] },
|
|
652
|
+
],
|
|
653
|
+
PostToolUse: [
|
|
654
|
+
{ hooks: [{ type: 'command', command: 'node /home/.claude/hooks/df-command-usage.js' }] },
|
|
655
|
+
{ hooks: [{ type: 'command', command: 'node /home/.claude/hooks/df-tool-usage.js' }] },
|
|
656
|
+
],
|
|
657
|
+
SessionEnd: [
|
|
658
|
+
{ hooks: [{ type: 'command', command: 'node /home/.claude/hooks/df-command-usage.js' }] },
|
|
659
|
+
{ hooks: [{ type: 'command', command: 'node /usr/local/keep.js' }] },
|
|
660
|
+
],
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
removeDeepflowHooks(settings);
|
|
664
|
+
assert.equal(settings.hooks.PreToolUse.length, 1);
|
|
665
|
+
assert.ok(settings.hooks.PreToolUse[0].hooks[0].command.includes('my-custom.js'));
|
|
666
|
+
assert.ok(!('PostToolUse' in settings.hooks), 'PostToolUse should be deleted when only deepflow hooks');
|
|
667
|
+
assert.equal(settings.hooks.SessionEnd.length, 1);
|
|
668
|
+
assert.ok(settings.hooks.SessionEnd[0].hooks[0].command.includes('keep.js'));
|
|
665
669
|
});
|
|
666
670
|
|
|
667
|
-
test('
|
|
668
|
-
const
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
!cmd.includes('df-execution-history') &&
|
|
679
|
-
!cmd.includes('df-worktree-guard') &&
|
|
680
|
-
!cmd.includes('df-snapshot-guard') &&
|
|
681
|
-
!cmd.includes('df-invariant-check') &&
|
|
682
|
-
!cmd.includes('df-command-usage');
|
|
683
|
-
});
|
|
684
|
-
|
|
685
|
-
assert.equal(filtered.length, 1);
|
|
686
|
-
assert.ok(filtered[0].hooks[0].command.includes('keep-me.js'));
|
|
671
|
+
test('removeDeepflowHooks deletes event key when array becomes empty', () => {
|
|
672
|
+
const { removeDeepflowHooks } = require('./install.js');
|
|
673
|
+
const settings = {
|
|
674
|
+
hooks: {
|
|
675
|
+
PreToolUse: [
|
|
676
|
+
{ hooks: [{ type: 'command', command: 'node /home/.claude/hooks/df-command-usage.js' }] },
|
|
677
|
+
],
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
removeDeepflowHooks(settings);
|
|
681
|
+
assert.ok(!('PreToolUse' in (settings.hooks || {})), 'PreToolUse should be deleted when empty');
|
|
687
682
|
});
|
|
688
683
|
|
|
689
|
-
test('
|
|
690
|
-
const
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
assert.equal(filtered.length, 1);
|
|
705
|
-
assert.ok(filtered[0].hooks[0].command.includes('keep.js'));
|
|
684
|
+
test('removeDeepflowHooks keeps non-deepflow hooks intact', () => {
|
|
685
|
+
const { removeDeepflowHooks } = require('./install.js');
|
|
686
|
+
const settings = {
|
|
687
|
+
hooks: {
|
|
688
|
+
PreToolUse: [
|
|
689
|
+
{ hooks: [{ type: 'command', command: 'node /home/.claude/hooks/df-command-usage.js' }] },
|
|
690
|
+
{ hooks: [{ type: 'command', command: 'node /usr/local/custom-pre-hook.js' }] },
|
|
691
|
+
],
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
removeDeepflowHooks(settings);
|
|
695
|
+
assert.ok('PreToolUse' in settings.hooks, 'PreToolUse should be kept when custom hooks remain');
|
|
696
|
+
assert.equal(settings.hooks.PreToolUse.length, 1);
|
|
697
|
+
assert.ok(settings.hooks.PreToolUse[0].hooks[0].command.includes('custom-pre-hook.js'));
|
|
706
698
|
});
|
|
707
699
|
|
|
708
|
-
// -- Uninstall
|
|
709
|
-
|
|
710
|
-
test('uninstall toRemove includes df-command-usage.js', () => {
|
|
711
|
-
const src = fs.readFileSync(path.resolve(__dirname, 'install.js'), 'utf8');
|
|
712
|
-
// Find the toRemove.push(...) line for hooks in uninstall
|
|
713
|
-
assert.ok(
|
|
714
|
-
src.includes("'hooks/df-command-usage.js'"),
|
|
715
|
-
'toRemove should include hooks/df-command-usage.js for uninstall'
|
|
716
|
-
);
|
|
717
|
-
});
|
|
700
|
+
// -- Uninstall: dynamic df-*.js discovery --
|
|
718
701
|
|
|
719
|
-
test('uninstall
|
|
702
|
+
test('uninstall dynamically discovers df-*.js hooks to remove', () => {
|
|
720
703
|
const src = fs.readFileSync(path.resolve(__dirname, 'install.js'), 'utf8');
|
|
721
|
-
// In the uninstall function, the SessionEnd filter should include df-command-usage
|
|
722
|
-
// Find the uninstall section's SessionEnd filter
|
|
723
704
|
const uninstallSection = src.match(/async function uninstall[\s\S]+$/);
|
|
724
705
|
assert.ok(uninstallSection, 'Should have uninstall function');
|
|
725
|
-
// Check SessionEnd filter in uninstall includes command-usage
|
|
726
|
-
const sessionEndFilter = uninstallSection[0].match(/SessionEnd[\s\S]*?\.filter[\s\S]*?\);/);
|
|
727
|
-
assert.ok(sessionEndFilter, 'Should have SessionEnd filter in uninstall');
|
|
728
706
|
assert.ok(
|
|
729
|
-
|
|
730
|
-
'
|
|
707
|
+
uninstallSection[0].includes("file.startsWith('df-')") &&
|
|
708
|
+
uninstallSection[0].includes("file.endsWith('.js')"),
|
|
709
|
+
'Uninstall should dynamically find df-*.js hook files'
|
|
731
710
|
);
|
|
732
711
|
});
|
|
733
712
|
|
|
734
|
-
test('uninstall
|
|
713
|
+
test('uninstall uses removeDeepflowHooks for settings cleanup', () => {
|
|
735
714
|
const src = fs.readFileSync(path.resolve(__dirname, 'install.js'), 'utf8');
|
|
736
715
|
const uninstallSection = src.match(/async function uninstall[\s\S]+$/);
|
|
737
|
-
const postToolUseFilter = uninstallSection[0].match(/PostToolUse[\s\S]*?\.filter[\s\S]*?\);/);
|
|
738
|
-
assert.ok(postToolUseFilter, 'Should have PostToolUse filter in uninstall');
|
|
739
716
|
assert.ok(
|
|
740
|
-
|
|
741
|
-
'Uninstall
|
|
717
|
+
uninstallSection[0].includes('removeDeepflowHooks'),
|
|
718
|
+
'Uninstall should use removeDeepflowHooks for generic cleanup'
|
|
742
719
|
);
|
|
743
720
|
});
|
|
744
721
|
|
|
745
|
-
test('uninstall
|
|
722
|
+
test('uninstall removes hooks/lib directory', () => {
|
|
746
723
|
const src = fs.readFileSync(path.resolve(__dirname, 'install.js'), 'utf8');
|
|
747
724
|
const uninstallSection = src.match(/async function uninstall[\s\S]+$/);
|
|
748
725
|
assert.ok(
|
|
749
|
-
uninstallSection[0].includes(
|
|
750
|
-
'Uninstall
|
|
751
|
-
);
|
|
752
|
-
// Verify it filters out df-command-usage from PreToolUse
|
|
753
|
-
const preToolUseFilter = uninstallSection[0].match(/PreToolUse[\s\S]*?\.filter[\s\S]*?\);/);
|
|
754
|
-
assert.ok(preToolUseFilter, 'Should have PreToolUse filter in uninstall');
|
|
755
|
-
assert.ok(
|
|
756
|
-
preToolUseFilter[0].includes('df-command-usage'),
|
|
757
|
-
'Uninstall PreToolUse filter should remove df-command-usage hooks'
|
|
726
|
+
uninstallSection[0].includes("hooks/lib"),
|
|
727
|
+
'Uninstall should remove hooks/lib directory'
|
|
758
728
|
);
|
|
759
729
|
});
|
|
760
|
-
|
|
761
|
-
test('uninstall deletes PreToolUse key when array becomes empty', () => {
|
|
762
|
-
// Reproduce the uninstall logic for PreToolUse
|
|
763
|
-
const settings = {
|
|
764
|
-
hooks: {
|
|
765
|
-
PreToolUse: [
|
|
766
|
-
{ hooks: [{ type: 'command', command: 'node /home/.claude/hooks/df-command-usage.js' }] },
|
|
767
|
-
],
|
|
768
|
-
}
|
|
769
|
-
};
|
|
770
|
-
|
|
771
|
-
settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(hook => {
|
|
772
|
-
const cmd = hook.hooks?.[0]?.command || '';
|
|
773
|
-
return !cmd.includes('df-command-usage');
|
|
774
|
-
});
|
|
775
|
-
if (settings.hooks.PreToolUse.length === 0) {
|
|
776
|
-
delete settings.hooks.PreToolUse;
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
assert.ok(!('PreToolUse' in settings.hooks), 'PreToolUse should be deleted when empty after filtering');
|
|
780
|
-
});
|
|
781
|
-
|
|
782
|
-
test('uninstall keeps PreToolUse when non-deepflow hooks remain', () => {
|
|
783
|
-
const settings = {
|
|
784
|
-
hooks: {
|
|
785
|
-
PreToolUse: [
|
|
786
|
-
{ hooks: [{ type: 'command', command: 'node /home/.claude/hooks/df-command-usage.js' }] },
|
|
787
|
-
{ hooks: [{ type: 'command', command: 'node /usr/local/custom-pre-hook.js' }] },
|
|
788
|
-
],
|
|
789
|
-
}
|
|
790
|
-
};
|
|
791
|
-
|
|
792
|
-
settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(hook => {
|
|
793
|
-
const cmd = hook.hooks?.[0]?.command || '';
|
|
794
|
-
return !cmd.includes('df-command-usage');
|
|
795
|
-
});
|
|
796
|
-
if (settings.hooks.PreToolUse.length === 0) {
|
|
797
|
-
delete settings.hooks.PreToolUse;
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
assert.ok('PreToolUse' in settings.hooks, 'PreToolUse should be kept when custom hooks remain');
|
|
801
|
-
assert.equal(settings.hooks.PreToolUse.length, 1);
|
|
802
|
-
assert.ok(settings.hooks.PreToolUse[0].hooks[0].command.includes('custom-pre-hook.js'));
|
|
803
|
-
});
|
|
804
730
|
});
|
|
805
731
|
|
|
806
732
|
// ---------------------------------------------------------------------------
|