chrome-cdp-cli 1.8.1 → 2.0.0
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/README.md +105 -16
- package/dist/cli/ArgumentParser.js +506 -0
- package/dist/cli/CLIApplication.js +91 -6
- package/dist/cli/CLIInterface.js +25 -82
- package/dist/cli/CommandRouter.js +4 -4
- package/dist/cli/CommandSchemaRegistry.js +614 -0
- package/dist/cli/EnhancedCLIInterface.js +230 -0
- package/dist/cli/HelpSystem.js +683 -0
- package/dist/cli/OutputFormatter.js +367 -0
- package/dist/cli/OutputManager.js +151 -0
- package/dist/cli/demo-enhanced-help.js +88 -0
- package/dist/cli/demo-enhanced-parser.js +79 -0
- package/dist/cli/demo-output-formatting.js +261 -0
- package/dist/cli/index.js +24 -4
- package/dist/cli/interfaces/ArgumentParser.js +2 -0
- package/dist/config/ConfigurationManager.js +357 -0
- package/dist/config/example.js +54 -0
- package/dist/config/index.js +21 -0
- package/dist/config/interfaces.js +35 -0
- package/dist/config/utils.js +238 -0
- package/dist/handlers/EvaluateScriptHandler.js +55 -2
- package/dist/handlers/InstallClaudeSkillHandler.js +271 -124
- package/dist/handlers/InstallCursorCommandHandler.js +32 -133
- package/dist/handlers/ListConsoleMessagesHandler.js +63 -8
- package/dist/handlers/ListNetworkRequestsHandler.js +37 -3
- package/dist/handlers/TakeSnapshotHandler.js +28 -8
- package/dist/handlers/index.js +0 -2
- package/dist/proxy/server/MessageStore.js +11 -80
- package/dist/utils/logger.js +1 -1
- package/package.json +3 -1
- package/dist/handlers/GetConsoleMessageHandler.js +0 -161
- package/dist/handlers/GetNetworkRequestHandler.js +0 -108
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
#!/usr/bin/env ts-node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.demonstrateOutputFormats = demonstrateOutputFormats;
|
|
5
|
+
const OutputManager_1 = require("./OutputManager");
|
|
6
|
+
const OutputFormatter_1 = require("./OutputFormatter");
|
|
7
|
+
const configs = {
|
|
8
|
+
text: {
|
|
9
|
+
host: 'localhost',
|
|
10
|
+
port: 9222,
|
|
11
|
+
outputFormat: 'text',
|
|
12
|
+
verbose: false,
|
|
13
|
+
quiet: false,
|
|
14
|
+
timeout: 30000,
|
|
15
|
+
debug: false
|
|
16
|
+
},
|
|
17
|
+
json: {
|
|
18
|
+
host: 'localhost',
|
|
19
|
+
port: 9222,
|
|
20
|
+
outputFormat: 'json',
|
|
21
|
+
verbose: false,
|
|
22
|
+
quiet: false,
|
|
23
|
+
timeout: 30000,
|
|
24
|
+
debug: false
|
|
25
|
+
},
|
|
26
|
+
yaml: {
|
|
27
|
+
host: 'localhost',
|
|
28
|
+
port: 9222,
|
|
29
|
+
outputFormat: 'yaml',
|
|
30
|
+
verbose: false,
|
|
31
|
+
quiet: false,
|
|
32
|
+
timeout: 30000,
|
|
33
|
+
debug: false
|
|
34
|
+
},
|
|
35
|
+
verbose: {
|
|
36
|
+
host: 'localhost',
|
|
37
|
+
port: 9222,
|
|
38
|
+
outputFormat: 'text',
|
|
39
|
+
verbose: true,
|
|
40
|
+
quiet: false,
|
|
41
|
+
timeout: 30000,
|
|
42
|
+
debug: false
|
|
43
|
+
},
|
|
44
|
+
quiet: {
|
|
45
|
+
host: 'localhost',
|
|
46
|
+
port: 9222,
|
|
47
|
+
outputFormat: 'text',
|
|
48
|
+
verbose: false,
|
|
49
|
+
quiet: true,
|
|
50
|
+
timeout: 30000,
|
|
51
|
+
debug: false
|
|
52
|
+
},
|
|
53
|
+
debug: {
|
|
54
|
+
host: 'localhost',
|
|
55
|
+
port: 9222,
|
|
56
|
+
outputFormat: 'text',
|
|
57
|
+
verbose: true,
|
|
58
|
+
quiet: false,
|
|
59
|
+
timeout: 30000,
|
|
60
|
+
debug: true
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const sampleResults = {
|
|
64
|
+
simpleSuccess: {
|
|
65
|
+
success: true,
|
|
66
|
+
data: 'Operation completed successfully'
|
|
67
|
+
},
|
|
68
|
+
consoleMessages: {
|
|
69
|
+
success: true,
|
|
70
|
+
data: {
|
|
71
|
+
messages: [
|
|
72
|
+
{
|
|
73
|
+
type: 'log',
|
|
74
|
+
text: 'Application started',
|
|
75
|
+
timestamp: Date.now() - 5000,
|
|
76
|
+
args: []
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
type: 'warn',
|
|
80
|
+
text: 'Deprecated API usage detected',
|
|
81
|
+
timestamp: Date.now() - 3000,
|
|
82
|
+
args: ['api.old()']
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
type: 'error',
|
|
86
|
+
text: 'Failed to load resource',
|
|
87
|
+
timestamp: Date.now() - 1000,
|
|
88
|
+
args: ['https://example.com/missing.js']
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
},
|
|
92
|
+
dataSource: 'proxy',
|
|
93
|
+
hasHistoricalData: true
|
|
94
|
+
},
|
|
95
|
+
networkRequests: {
|
|
96
|
+
success: true,
|
|
97
|
+
data: {
|
|
98
|
+
requests: [
|
|
99
|
+
{
|
|
100
|
+
requestId: '1',
|
|
101
|
+
url: 'https://api.example.com/users',
|
|
102
|
+
method: 'GET',
|
|
103
|
+
timestamp: Date.now() - 2000,
|
|
104
|
+
status: 200
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
requestId: '2',
|
|
108
|
+
url: 'https://api.example.com/posts',
|
|
109
|
+
method: 'POST',
|
|
110
|
+
timestamp: Date.now() - 1000,
|
|
111
|
+
status: 201
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
},
|
|
115
|
+
dataSource: 'direct',
|
|
116
|
+
hasHistoricalData: false
|
|
117
|
+
},
|
|
118
|
+
error: {
|
|
119
|
+
success: false,
|
|
120
|
+
error: 'Connection timeout: Unable to connect to Chrome DevTools',
|
|
121
|
+
exitCode: 1
|
|
122
|
+
},
|
|
123
|
+
complexData: {
|
|
124
|
+
success: true,
|
|
125
|
+
data: {
|
|
126
|
+
pageInfo: {
|
|
127
|
+
title: 'Example Page',
|
|
128
|
+
url: 'https://example.com',
|
|
129
|
+
loadTime: 1250
|
|
130
|
+
},
|
|
131
|
+
metrics: {
|
|
132
|
+
domContentLoaded: 800,
|
|
133
|
+
firstPaint: 900,
|
|
134
|
+
firstContentfulPaint: 950
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
function printSeparator(title) {
|
|
140
|
+
console.log('\n' + '='.repeat(60));
|
|
141
|
+
console.log(` ${title}`);
|
|
142
|
+
console.log('='.repeat(60));
|
|
143
|
+
}
|
|
144
|
+
function printSubsection(title) {
|
|
145
|
+
console.log(`\n--- ${title} ---`);
|
|
146
|
+
}
|
|
147
|
+
async function demonstrateOutputFormats() {
|
|
148
|
+
const outputManager = new OutputManager_1.OutputManager();
|
|
149
|
+
const formatter = new OutputFormatter_1.OutputFormatter();
|
|
150
|
+
printSeparator('STANDARDIZED OUTPUT FORMATTING DEMO');
|
|
151
|
+
printSubsection('1. Output Formats (JSON, Text, YAML)');
|
|
152
|
+
const result = sampleResults.complexData;
|
|
153
|
+
console.log('\n📄 TEXT FORMAT:');
|
|
154
|
+
console.log(outputManager.formatOutput(result, configs.text));
|
|
155
|
+
console.log('\n📋 JSON FORMAT:');
|
|
156
|
+
console.log(outputManager.formatOutput(result, configs.json));
|
|
157
|
+
console.log('\n📝 YAML FORMAT:');
|
|
158
|
+
console.log(outputManager.formatOutput(result, configs.yaml));
|
|
159
|
+
printSubsection('2. Verbose Mode with Timing Information');
|
|
160
|
+
const timing = (0, OutputFormatter_1.createTimingInfo)('screenshot-command', Date.now() - 1500, Date.now());
|
|
161
|
+
const metadata = {
|
|
162
|
+
command: 'screenshot',
|
|
163
|
+
args: { filename: 'test.png', fullPage: true },
|
|
164
|
+
config: configs.verbose
|
|
165
|
+
};
|
|
166
|
+
const enhancedResult = (0, OutputFormatter_1.enhanceCommandResult)(sampleResults.simpleSuccess, timing, metadata);
|
|
167
|
+
console.log('\n🔍 VERBOSE OUTPUT:');
|
|
168
|
+
console.log(formatter.formatOutput(enhancedResult, {
|
|
169
|
+
format: 'text',
|
|
170
|
+
mode: { verbose: true, quiet: false, debug: false },
|
|
171
|
+
includeMetadata: true,
|
|
172
|
+
includeTiming: true
|
|
173
|
+
}));
|
|
174
|
+
printSubsection('3. Quiet Mode (Minimal Output)');
|
|
175
|
+
console.log('\n🤫 QUIET MODE - Success with data:');
|
|
176
|
+
console.log(`"${outputManager.formatOutput(sampleResults.consoleMessages, configs.quiet)}"`);
|
|
177
|
+
console.log('\n🤫 QUIET MODE - Success without data:');
|
|
178
|
+
console.log(`"${outputManager.formatOutput(sampleResults.simpleSuccess, configs.quiet)}"`);
|
|
179
|
+
console.log('\n🤫 QUIET MODE - Error (still shown):');
|
|
180
|
+
console.log(outputManager.formatOutput(sampleResults.error, configs.quiet));
|
|
181
|
+
printSubsection('4. Debug Mode with Detailed Information');
|
|
182
|
+
const debugResult = (0, OutputFormatter_1.enhanceCommandResult)(sampleResults.error, timing, metadata);
|
|
183
|
+
console.log('\n🔧 DEBUG OUTPUT:');
|
|
184
|
+
console.log(formatter.formatOutput(debugResult, {
|
|
185
|
+
format: 'text',
|
|
186
|
+
mode: { verbose: true, quiet: false, debug: true },
|
|
187
|
+
includeMetadata: true,
|
|
188
|
+
includeTiming: true
|
|
189
|
+
}));
|
|
190
|
+
printSubsection('5. Console Messages Formatting');
|
|
191
|
+
console.log('\n📝 CONSOLE MESSAGES (Text):');
|
|
192
|
+
console.log(outputManager.formatOutput(sampleResults.consoleMessages, configs.text));
|
|
193
|
+
console.log('\n📝 CONSOLE MESSAGES (Verbose):');
|
|
194
|
+
console.log(outputManager.formatOutput(sampleResults.consoleMessages, configs.verbose));
|
|
195
|
+
printSubsection('6. Network Requests Formatting');
|
|
196
|
+
console.log('\n🌐 NETWORK REQUESTS:');
|
|
197
|
+
console.log(outputManager.formatOutput(sampleResults.networkRequests, configs.text));
|
|
198
|
+
printSubsection('7. Custom Output Templates');
|
|
199
|
+
formatter.registerTemplate({
|
|
200
|
+
name: 'status-only',
|
|
201
|
+
description: 'Show only success status',
|
|
202
|
+
template: 'Status: {{success}}',
|
|
203
|
+
variables: ['success']
|
|
204
|
+
});
|
|
205
|
+
formatter.registerTemplate({
|
|
206
|
+
name: 'detailed-summary',
|
|
207
|
+
description: 'Detailed summary with timing',
|
|
208
|
+
template: '✅ {{success}} | ⏱️ {{timing.duration}}ms | 📋 {{metadata.command}}',
|
|
209
|
+
variables: ['success', 'timing', 'metadata']
|
|
210
|
+
});
|
|
211
|
+
console.log('\n🎨 CUSTOM TEMPLATE - Status Only:');
|
|
212
|
+
console.log(formatter.formatOutput(sampleResults.simpleSuccess, {
|
|
213
|
+
format: 'text',
|
|
214
|
+
mode: { verbose: false, quiet: false, debug: false },
|
|
215
|
+
template: 'status-only'
|
|
216
|
+
}));
|
|
217
|
+
console.log('\n🎨 CUSTOM TEMPLATE - Detailed Summary:');
|
|
218
|
+
console.log(formatter.formatOutput(enhancedResult, {
|
|
219
|
+
format: 'text',
|
|
220
|
+
mode: { verbose: false, quiet: false, debug: false },
|
|
221
|
+
template: 'detailed-summary'
|
|
222
|
+
}));
|
|
223
|
+
printSubsection('8. Consistent Error Handling');
|
|
224
|
+
console.log('\n❌ ERROR (Text):');
|
|
225
|
+
console.log(outputManager.formatOutput(sampleResults.error, configs.text));
|
|
226
|
+
console.log('\n❌ ERROR (JSON):');
|
|
227
|
+
console.log(outputManager.formatOutput(sampleResults.error, configs.json));
|
|
228
|
+
console.log('\n❌ ERROR (Debug):');
|
|
229
|
+
console.log(outputManager.formatOutput(sampleResults.error, configs.debug));
|
|
230
|
+
printSubsection('9. Utility Functions');
|
|
231
|
+
console.log('\n🛠️ VALIDATION ERRORS:');
|
|
232
|
+
console.log(outputManager.formatValidationErrors([
|
|
233
|
+
{ field: 'host', message: 'Invalid hostname format', suggestion: 'Use localhost or valid IP' },
|
|
234
|
+
{ field: 'port', message: 'Port must be between 1 and 65535' }
|
|
235
|
+
]));
|
|
236
|
+
console.log('\n📖 HELP INFORMATION:');
|
|
237
|
+
console.log(outputManager.formatHelpInfo('Screenshot Command', 'Capture a screenshot of the current page with various options.', [
|
|
238
|
+
'chrome-cdp-cli screenshot --filename=page.png',
|
|
239
|
+
'chrome-cdp-cli screenshot --full-page --format=jpeg'
|
|
240
|
+
]));
|
|
241
|
+
printSubsection('10. Performance and Timing');
|
|
242
|
+
console.log('\n⏱️ TIMING INFORMATION:');
|
|
243
|
+
console.log(outputManager.getTimingInfo(Date.now() - 2500, Date.now(), 'complex-operation'));
|
|
244
|
+
console.log('\n📊 DATA SOURCE INFORMATION:');
|
|
245
|
+
console.log(outputManager.formatDataSourceInfo('proxy', true));
|
|
246
|
+
console.log(outputManager.formatDataSourceInfo('direct', false));
|
|
247
|
+
printSeparator('DEMO COMPLETE');
|
|
248
|
+
console.log('\n✅ All output formatting features demonstrated successfully!');
|
|
249
|
+
console.log('\n📋 Key Features:');
|
|
250
|
+
console.log(' • Multiple output formats (JSON, Text, YAML)');
|
|
251
|
+
console.log(' • Quiet and verbose modes');
|
|
252
|
+
console.log(' • Custom output templates');
|
|
253
|
+
console.log(' • Timing information for verbose mode');
|
|
254
|
+
console.log(' • Consistent error handling');
|
|
255
|
+
console.log(' • Smart data formatting (console messages, network requests)');
|
|
256
|
+
console.log(' • Debug mode with detailed information');
|
|
257
|
+
console.log(' • Utility functions for validation and help');
|
|
258
|
+
}
|
|
259
|
+
if (require.main === module) {
|
|
260
|
+
demonstrateOutputFormats().catch(console.error);
|
|
261
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -1,12 +1,32 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
2
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
17
|
+
exports.CommandRouter = exports.CommandRegistry = exports.CLIInterface = exports.CLIApplication = exports.CommandSchemaRegistry = exports.EnhancedCLIInterface = exports.ArgumentParser = void 0;
|
|
18
|
+
var ArgumentParser_1 = require("./ArgumentParser");
|
|
19
|
+
Object.defineProperty(exports, "ArgumentParser", { enumerable: true, get: function () { return ArgumentParser_1.ArgumentParser; } });
|
|
20
|
+
var EnhancedCLIInterface_1 = require("./EnhancedCLIInterface");
|
|
21
|
+
Object.defineProperty(exports, "EnhancedCLIInterface", { enumerable: true, get: function () { return EnhancedCLIInterface_1.EnhancedCLIInterface; } });
|
|
22
|
+
var CommandSchemaRegistry_1 = require("./CommandSchemaRegistry");
|
|
23
|
+
Object.defineProperty(exports, "CommandSchemaRegistry", { enumerable: true, get: function () { return CommandSchemaRegistry_1.CommandSchemaRegistry; } });
|
|
24
|
+
__exportStar(require("./interfaces/ArgumentParser"), exports);
|
|
25
|
+
var CLIApplication_1 = require("./CLIApplication");
|
|
26
|
+
Object.defineProperty(exports, "CLIApplication", { enumerable: true, get: function () { return CLIApplication_1.CLIApplication; } });
|
|
4
27
|
var CLIInterface_1 = require("./CLIInterface");
|
|
5
28
|
Object.defineProperty(exports, "CLIInterface", { enumerable: true, get: function () { return CLIInterface_1.CLIInterface; } });
|
|
6
29
|
var CommandRegistry_1 = require("./CommandRegistry");
|
|
7
30
|
Object.defineProperty(exports, "CommandRegistry", { enumerable: true, get: function () { return CommandRegistry_1.CommandRegistry; } });
|
|
8
31
|
var CommandRouter_1 = require("./CommandRouter");
|
|
9
32
|
Object.defineProperty(exports, "CommandRouter", { enumerable: true, get: function () { return CommandRouter_1.CommandRouter; } });
|
|
10
|
-
Object.defineProperty(exports, "ExitCode", { enumerable: true, get: function () { return CommandRouter_1.ExitCode; } });
|
|
11
|
-
var CLIApplication_1 = require("./CLIApplication");
|
|
12
|
-
Object.defineProperty(exports, "CLIApplication", { enumerable: true, get: function () { return CLIApplication_1.CLIApplication; } });
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ConfigurationManager = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const yaml = __importStar(require("js-yaml"));
|
|
40
|
+
const interfaces_1 = require("./interfaces");
|
|
41
|
+
class ConfigurationManager {
|
|
42
|
+
constructor() {
|
|
43
|
+
this.configSources = [];
|
|
44
|
+
this.availableProfiles = [];
|
|
45
|
+
}
|
|
46
|
+
async loadConfiguration(sources) {
|
|
47
|
+
this.configSources = sources.sort((a, b) => a.priority - b.priority);
|
|
48
|
+
let mergedConfig = { ...interfaces_1.DEFAULT_CLI_CONFIG };
|
|
49
|
+
for (const source of this.configSources) {
|
|
50
|
+
mergedConfig = this.mergeConfiguration(mergedConfig, source.data);
|
|
51
|
+
}
|
|
52
|
+
const validation = this.validateConfiguration(mergedConfig);
|
|
53
|
+
if (!validation.valid) {
|
|
54
|
+
const errorMessages = validation.errors.map(e => `${e.field}: ${e.message}`).join(', ');
|
|
55
|
+
throw new Error(`Configuration validation failed: ${errorMessages}`);
|
|
56
|
+
}
|
|
57
|
+
this.loadedConfig = mergedConfig;
|
|
58
|
+
return mergedConfig;
|
|
59
|
+
}
|
|
60
|
+
validateConfiguration(config) {
|
|
61
|
+
const errors = [];
|
|
62
|
+
const warnings = [];
|
|
63
|
+
if (!config.host || typeof config.host !== 'string') {
|
|
64
|
+
errors.push({
|
|
65
|
+
field: 'host',
|
|
66
|
+
message: 'Host must be a non-empty string',
|
|
67
|
+
code: 'INVALID_HOST',
|
|
68
|
+
suggestion: 'Use a valid hostname like "localhost" or IP address'
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
if (typeof config.port !== 'number' || config.port < 1 || config.port > 65535) {
|
|
72
|
+
errors.push({
|
|
73
|
+
field: 'port',
|
|
74
|
+
message: 'Port must be a number between 1 and 65535',
|
|
75
|
+
code: 'INVALID_PORT',
|
|
76
|
+
suggestion: 'Use a valid port number like 9222'
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (typeof config.timeout !== 'number' || config.timeout < 1) {
|
|
80
|
+
errors.push({
|
|
81
|
+
field: 'timeout',
|
|
82
|
+
message: 'Timeout must be a positive number',
|
|
83
|
+
code: 'INVALID_TIMEOUT',
|
|
84
|
+
suggestion: 'Use a timeout value in milliseconds, e.g., 30000'
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
const validFormats = ['json', 'text', 'yaml'];
|
|
88
|
+
if (!validFormats.includes(config.outputFormat)) {
|
|
89
|
+
errors.push({
|
|
90
|
+
field: 'outputFormat',
|
|
91
|
+
message: `Output format must be one of: ${validFormats.join(', ')}`,
|
|
92
|
+
code: 'INVALID_OUTPUT_FORMAT',
|
|
93
|
+
suggestion: 'Use "json", "text", or "yaml"'
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
const booleanFields = ['verbose', 'quiet', 'debug'];
|
|
97
|
+
for (const field of booleanFields) {
|
|
98
|
+
if (typeof config[field] !== 'boolean') {
|
|
99
|
+
errors.push({
|
|
100
|
+
field,
|
|
101
|
+
message: `${field} must be a boolean`,
|
|
102
|
+
code: 'INVALID_BOOLEAN',
|
|
103
|
+
suggestion: 'Use true or false'
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (!Array.isArray(config.pluginDirs)) {
|
|
108
|
+
errors.push({
|
|
109
|
+
field: 'pluginDirs',
|
|
110
|
+
message: 'Plugin directories must be an array',
|
|
111
|
+
code: 'INVALID_PLUGIN_DIRS',
|
|
112
|
+
suggestion: 'Use an array of directory paths'
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
for (let i = 0; i < config.pluginDirs.length; i++) {
|
|
117
|
+
if (typeof config.pluginDirs[i] !== 'string') {
|
|
118
|
+
errors.push({
|
|
119
|
+
field: `pluginDirs[${i}]`,
|
|
120
|
+
message: 'Plugin directory path must be a string',
|
|
121
|
+
code: 'INVALID_PLUGIN_DIR_PATH',
|
|
122
|
+
suggestion: 'Use a valid directory path'
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (typeof config.aliases !== 'object' || config.aliases === null) {
|
|
128
|
+
errors.push({
|
|
129
|
+
field: 'aliases',
|
|
130
|
+
message: 'Aliases must be an object',
|
|
131
|
+
code: 'INVALID_ALIASES',
|
|
132
|
+
suggestion: 'Use an object with string keys and values'
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
for (const [alias, command] of Object.entries(config.aliases)) {
|
|
137
|
+
if (typeof command !== 'string') {
|
|
138
|
+
errors.push({
|
|
139
|
+
field: `aliases.${alias}`,
|
|
140
|
+
message: 'Alias target must be a string',
|
|
141
|
+
code: 'INVALID_ALIAS_TARGET',
|
|
142
|
+
suggestion: 'Use a valid command name'
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (typeof config.commands !== 'object' || config.commands === null) {
|
|
148
|
+
errors.push({
|
|
149
|
+
field: 'commands',
|
|
150
|
+
message: 'Commands configuration must be an object',
|
|
151
|
+
code: 'INVALID_COMMANDS_CONFIG',
|
|
152
|
+
suggestion: 'Use an object with command names as keys'
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
if (config.verbose && config.quiet) {
|
|
156
|
+
warnings.push({
|
|
157
|
+
field: 'verbose,quiet',
|
|
158
|
+
message: 'Verbose and quiet modes are conflicting',
|
|
159
|
+
code: 'CONFLICTING_OPTIONS'
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
valid: errors.length === 0,
|
|
164
|
+
errors,
|
|
165
|
+
warnings
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
getConfigurationPrecedence() {
|
|
169
|
+
return [...this.configSources];
|
|
170
|
+
}
|
|
171
|
+
resolveConfigValue(key, type) {
|
|
172
|
+
if (!this.loadedConfig) {
|
|
173
|
+
throw new Error('Configuration not loaded. Call loadConfiguration first.');
|
|
174
|
+
}
|
|
175
|
+
const value = this.getNestedValue(this.loadedConfig, key);
|
|
176
|
+
if (value === undefined) {
|
|
177
|
+
throw new Error(`Configuration key "${key}" not found`);
|
|
178
|
+
}
|
|
179
|
+
if (!this.validateValueType(value, type)) {
|
|
180
|
+
throw new Error(`Configuration key "${key}" has invalid type. Expected ${type}, got ${typeof value}`);
|
|
181
|
+
}
|
|
182
|
+
return value;
|
|
183
|
+
}
|
|
184
|
+
async loadYAMLConfig(filePath) {
|
|
185
|
+
try {
|
|
186
|
+
const content = await fs.promises.readFile(filePath, 'utf-8');
|
|
187
|
+
let config;
|
|
188
|
+
try {
|
|
189
|
+
config = yaml.load(content);
|
|
190
|
+
}
|
|
191
|
+
catch (yamlError) {
|
|
192
|
+
config = JSON.parse(content);
|
|
193
|
+
}
|
|
194
|
+
if (typeof config !== 'object' || config === null) {
|
|
195
|
+
throw new Error('Configuration file must contain a valid object');
|
|
196
|
+
}
|
|
197
|
+
if (config.profiles) {
|
|
198
|
+
this.availableProfiles = Object.keys(config.profiles);
|
|
199
|
+
}
|
|
200
|
+
return config;
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
throw new Error(`Failed to load configuration file "${filePath}": ${error instanceof Error ? error.message : String(error)}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
loadEnvironmentConfig() {
|
|
207
|
+
const envConfig = {};
|
|
208
|
+
const envMappings = {
|
|
209
|
+
[`${interfaces_1.ENV_PREFIX}HOST`]: 'host',
|
|
210
|
+
[`${interfaces_1.ENV_PREFIX}PORT`]: 'port',
|
|
211
|
+
[`${interfaces_1.ENV_PREFIX}TIMEOUT`]: 'timeout',
|
|
212
|
+
[`${interfaces_1.ENV_PREFIX}OUTPUT_FORMAT`]: 'outputFormat',
|
|
213
|
+
[`${interfaces_1.ENV_PREFIX}VERBOSE`]: 'verbose',
|
|
214
|
+
[`${interfaces_1.ENV_PREFIX}QUIET`]: 'quiet',
|
|
215
|
+
[`${interfaces_1.ENV_PREFIX}DEBUG`]: 'debug',
|
|
216
|
+
[`${interfaces_1.ENV_PREFIX}PROFILE`]: 'profile',
|
|
217
|
+
[`${interfaces_1.ENV_PREFIX}CONFIG_FILE`]: 'configFile'
|
|
218
|
+
};
|
|
219
|
+
for (const [envVar, configKey] of Object.entries(envMappings)) {
|
|
220
|
+
const value = process.env[envVar];
|
|
221
|
+
if (value !== undefined) {
|
|
222
|
+
envConfig[configKey] = this.parseEnvironmentValue(value, configKey);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const pluginDirs = process.env[`${interfaces_1.ENV_PREFIX}PLUGIN_DIRS`];
|
|
226
|
+
if (pluginDirs) {
|
|
227
|
+
envConfig.pluginDirs = pluginDirs.split(',').map(dir => dir.trim());
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
type: 'env',
|
|
231
|
+
priority: interfaces_1.CONFIG_PRIORITIES.env,
|
|
232
|
+
data: envConfig,
|
|
233
|
+
source: 'environment variables'
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
getAvailableProfiles() {
|
|
237
|
+
return [...this.availableProfiles];
|
|
238
|
+
}
|
|
239
|
+
async loadProfile(profileName) {
|
|
240
|
+
const configFiles = await this.findConfigurationFiles();
|
|
241
|
+
for (const configFile of configFiles) {
|
|
242
|
+
try {
|
|
243
|
+
const config = await this.loadYAMLConfig(configFile);
|
|
244
|
+
if (config.profiles && config.profiles[profileName]) {
|
|
245
|
+
return config.profiles[profileName];
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
throw new Error(`Profile "${profileName}" not found in any configuration file`);
|
|
253
|
+
}
|
|
254
|
+
async createConfigurationSources(cliOptions = {}) {
|
|
255
|
+
const sources = [];
|
|
256
|
+
sources.push({
|
|
257
|
+
type: 'default',
|
|
258
|
+
priority: interfaces_1.CONFIG_PRIORITIES.default,
|
|
259
|
+
data: { ...interfaces_1.DEFAULT_CLI_CONFIG },
|
|
260
|
+
source: 'defaults'
|
|
261
|
+
});
|
|
262
|
+
const configFile = cliOptions.configFile || await this.findDefaultConfigFile();
|
|
263
|
+
if (configFile) {
|
|
264
|
+
try {
|
|
265
|
+
const fileConfig = await this.loadYAMLConfig(configFile);
|
|
266
|
+
let profileConfig = {};
|
|
267
|
+
const profileName = cliOptions.profile || fileConfig.profile;
|
|
268
|
+
if (profileName && fileConfig.profiles && fileConfig.profiles[profileName]) {
|
|
269
|
+
profileConfig = fileConfig.profiles[profileName];
|
|
270
|
+
}
|
|
271
|
+
const mergedFileConfig = this.mergeConfiguration(fileConfig, profileConfig);
|
|
272
|
+
sources.push({
|
|
273
|
+
type: 'file',
|
|
274
|
+
priority: interfaces_1.CONFIG_PRIORITIES.file,
|
|
275
|
+
data: mergedFileConfig,
|
|
276
|
+
source: configFile
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
if (cliOptions.configFile) {
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
sources.push(this.loadEnvironmentConfig());
|
|
286
|
+
sources.push({
|
|
287
|
+
type: 'cli',
|
|
288
|
+
priority: interfaces_1.CONFIG_PRIORITIES.cli,
|
|
289
|
+
data: cliOptions,
|
|
290
|
+
source: 'command line'
|
|
291
|
+
});
|
|
292
|
+
return sources;
|
|
293
|
+
}
|
|
294
|
+
async findConfigurationFiles() {
|
|
295
|
+
const configFiles = [];
|
|
296
|
+
for (const searchPath of interfaces_1.DEFAULT_CONFIG_PATHS) {
|
|
297
|
+
for (const fileName of interfaces_1.DEFAULT_CONFIG_FILES) {
|
|
298
|
+
const filePath = path.join(searchPath, fileName);
|
|
299
|
+
try {
|
|
300
|
+
await fs.promises.access(filePath, fs.constants.R_OK);
|
|
301
|
+
configFiles.push(filePath);
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return configFiles;
|
|
308
|
+
}
|
|
309
|
+
async findDefaultConfigFile() {
|
|
310
|
+
const configFiles = await this.findConfigurationFiles();
|
|
311
|
+
return configFiles.length > 0 ? configFiles[0] : null;
|
|
312
|
+
}
|
|
313
|
+
mergeConfiguration(base, override) {
|
|
314
|
+
const result = { ...base };
|
|
315
|
+
for (const [key, value] of Object.entries(override)) {
|
|
316
|
+
if (value !== undefined && value !== null) {
|
|
317
|
+
if (typeof value === 'object' && !Array.isArray(value) && typeof result[key] === 'object' && !Array.isArray(result[key])) {
|
|
318
|
+
result[key] = this.mergeConfiguration(result[key], value);
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
result[key] = value;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return result;
|
|
326
|
+
}
|
|
327
|
+
parseEnvironmentValue(value, key) {
|
|
328
|
+
if (['verbose', 'quiet', 'debug'].includes(key)) {
|
|
329
|
+
return value.toLowerCase() === 'true' || value === '1';
|
|
330
|
+
}
|
|
331
|
+
if (['port', 'timeout'].includes(key)) {
|
|
332
|
+
const num = parseInt(value, 10);
|
|
333
|
+
return isNaN(num) ? value : num;
|
|
334
|
+
}
|
|
335
|
+
return value;
|
|
336
|
+
}
|
|
337
|
+
getNestedValue(obj, key) {
|
|
338
|
+
return key.split('.').reduce((current, prop) => current?.[prop], obj);
|
|
339
|
+
}
|
|
340
|
+
validateValueType(value, expectedType) {
|
|
341
|
+
switch (expectedType) {
|
|
342
|
+
case 'string':
|
|
343
|
+
return typeof value === 'string';
|
|
344
|
+
case 'number':
|
|
345
|
+
return typeof value === 'number';
|
|
346
|
+
case 'boolean':
|
|
347
|
+
return typeof value === 'boolean';
|
|
348
|
+
case 'array':
|
|
349
|
+
return Array.isArray(value);
|
|
350
|
+
case 'object':
|
|
351
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
352
|
+
default:
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
exports.ConfigurationManager = ConfigurationManager;
|