Skip to content

Commit 627e984

Browse files
committed
feat: implement Phase 4 & 5 - integration and testing
Phase 4 - Build Configuration System: - Integrated modular shell system with main entry point (src/index.ts) - Modified main() to call loadShells() on startup using getBuildConfig() - Updated getEnabledShells() to use registry with backward compatibility - Added build scripts to package.json for different presets (full, windows, unix, gitbash-only, cmd-only, powershell-only) - Added cross-env dependency for cross-platform environment variable support Phase 5 - Testing & Validation: - Fixed TypeScript errors in test files (mockImplementation() calls and spread operator type annotations) - Created comprehensive integration test suite (tests/integration/modularShellSystem.test.ts) - Integration tests cover: shell loading, registry integration, dynamic tool schemas, build configuration workflows, error handling - All 680 tests passing (67 test suites) Key Implementation Details: - getEnabledShells() now returns intersection of configured shells and loaded shells from registry - Backward compatibility: if registry is empty, returns all configured shells (for existing tests) - Dynamic tool schemas automatically reflect shells loaded in registry - Build presets enable customized builds with specific shell combinations
1 parent 689775e commit 627e984

File tree

7 files changed

+393
-10
lines changed

7 files changed

+393
-10
lines changed

package-lock.json

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@
3636
"scripts": {
3737
"clean": "shx rm -rf dist",
3838
"build": "tsc && shx chmod +x dist/index.js",
39+
"build:full": "cross-env SHELL_BUILD_PRESET=full npm run build",
40+
"build:windows": "cross-env SHELL_BUILD_PRESET=windows npm run build",
41+
"build:unix": "cross-env SHELL_BUILD_PRESET=unix npm run build",
42+
"build:gitbash": "cross-env SHELL_BUILD_PRESET=gitbash-only npm run build",
43+
"build:cmd": "cross-env SHELL_BUILD_PRESET=cmd-only npm run build",
44+
"build:powershell": "cross-env SHELL_BUILD_PRESET=powershell-only npm run build",
3945
"prepare": "npm run build",
4046
"watch": "tsc --watch",
4147
"start": "node dist/index.js",
@@ -60,6 +66,7 @@
6066
"@types/jest": "^29.5.14",
6167
"@types/node": "^20.11.0",
6268
"@types/yargs": "^17.0.33",
69+
"cross-env": "^7.0.3",
6370
"jest": "^29.7.0",
6471
"shx": "^0.3.4",
6572
"ts-jest": "^29.3.4",

src/build/__tests__/shell-config.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ describe('Build Configuration', () => {
103103
it('should warn for unknown preset and use default', () => {
104104
process.env.SHELL_BUILD_PRESET = 'unknown-preset';
105105

106-
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
106+
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
107107

108108
const config = getBuildConfig();
109109

src/core/__tests__/registry.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class MockShellA implements ShellPlugin {
1010
validateCommand = jest.fn(() => ({ valid: true }));
1111
validatePath = jest.fn(() => ({ valid: true }));
1212
getBlockedCommands = jest.fn(() => []);
13-
mergeConfig = jest.fn((base, override) => ({ ...base, ...override }));
13+
mergeConfig = jest.fn((base: ShellConfig, override: Partial<ShellConfig>) => ({ ...base, ...override } as ShellConfig));
1414
}
1515

1616
class MockShellB implements ShellPlugin {
@@ -20,7 +20,7 @@ class MockShellB implements ShellPlugin {
2020
validateCommand = jest.fn(() => ({ valid: true }));
2121
validatePath = jest.fn(() => ({ valid: true }));
2222
getBlockedCommands = jest.fn(() => []);
23-
mergeConfig = jest.fn((base, override) => ({ ...base, ...override }));
23+
mergeConfig = jest.fn((base: ShellConfig, override: Partial<ShellConfig>) => ({ ...base, ...override } as ShellConfig));
2424
}
2525

2626
describe('ShellRegistry', () => {
@@ -53,7 +53,7 @@ describe('ShellRegistry', () => {
5353
const shell1 = new MockShellA();
5454
const shell2 = new MockShellA();
5555

56-
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
56+
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
5757

5858
registry.register(shell1);
5959
registry.register(shell2);
@@ -79,7 +79,7 @@ describe('ShellRegistry', () => {
7979
});
8080

8181
it('should log when registering shell', () => {
82-
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
82+
const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
8383
const shell = new MockShellA();
8484

8585
registry.register(shell);

src/index.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ import { setDebugLogging, debugLog, debugWarn, errorLog } from './utils/log.js';
4242
import { truncateOutput, formatTruncatedOutput } from './utils/truncation.js';
4343
import { LogStorageManager } from './utils/logStorage.js';
4444
import { LogResourceHandler } from './utils/logResourceHandler.js';
45+
// Import modular shell system
46+
import { loadShells } from './shells/loader.js';
47+
import { shellRegistry } from './core/registry.js';
48+
import { getBuildConfig } from './build/shell-config.js';
4549

4650
const __filename = fileURLToPath(import.meta.url);
4751
const __dirname = dirname(__filename);
@@ -236,7 +240,18 @@ class CLIServer {
236240
}
237241

238242
private getEnabledShells(): string[] {
239-
return Array.from(this.resolvedConfigs.keys());
243+
// Return shells that are both loaded in the registry AND enabled in config
244+
const configShells = Array.from(this.resolvedConfigs.keys());
245+
const loadedShells = shellRegistry.getShellTypes();
246+
247+
// If registry is empty, return all config shells (backward compatibility)
248+
// Once fully migrated, this will enforce that shells must be in registry
249+
if (loadedShells.length === 0) {
250+
return configShells;
251+
}
252+
253+
// Intersection: shells that are in both lists
254+
return configShells.filter(shell => loadedShells.includes(shell));
240255
}
241256

242257
private validateSingleCommand(context: ValidationContext, command: string): void {
@@ -1096,7 +1111,15 @@ const main = async () => {
10961111

10971112
// Set debug logging based on CLI argument
10981113
setDebugLogging(Boolean(args.debug));
1099-
1114+
1115+
// Initialize modular shell system
1116+
const buildConfig = getBuildConfig();
1117+
await loadShells({
1118+
shells: buildConfig.includedShells,
1119+
verbose: buildConfig.verbose || Boolean(args.debug)
1120+
});
1121+
debugLog(`Loaded ${shellRegistry.getCount()} shell modules: ${shellRegistry.getShellTypes().join(', ')}`);
1122+
11001123
// Handle --init-config flag
11011124
if (args['init-config']) {
11021125
try {

src/shells/__tests__/loader.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ describe('Shell Loader', () => {
4343
});
4444

4545
it('should handle invalid shell types gracefully', async () => {
46-
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
46+
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
4747

4848
await loadShells({
4949
shells: ['gitbash', 'invalid-shell', 'powershell'],
@@ -128,7 +128,7 @@ describe('Shell Loader', () => {
128128
});
129129

130130
it('should support verbose mode', async () => {
131-
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
131+
const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
132132

133133
await loadShells({
134134
shells: ['gitbash'],
@@ -143,7 +143,7 @@ describe('Shell Loader', () => {
143143
});
144144

145145
it('should handle loading errors gracefully', async () => {
146-
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
146+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
147147

148148
// This should not throw even if there's an error
149149
await loadShells({

0 commit comments

Comments
 (0)