@tamyla/clodo-framework 4.5.1 โ†’ 4.6.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.
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Doctor Command Module
3
+ * Provides preflight diagnostic checks for Clodo services
4
+ */
5
+
6
+ import { ValidationHandler } from '../../src/service-management/handlers/ValidationHandler.js';
7
+ import chalk from 'chalk';
8
+ export function registerDoctorCommand(program) {
9
+ program.command('doctor').description('Run preflight diagnostic checks for service health and deployment readiness').option('--json', 'Output results in JSON format for machine consumption').option('--fix', 'Attempt to automatically fix detected issues').option('--strict', 'Treat warnings as errors (non-zero exit code)').option('--service-path <path>', 'Path to service directory (defaults to current directory)').action(async options => {
10
+ try {
11
+ const handler = new ValidationHandler({
12
+ strict: options.strict
13
+ });
14
+ const results = await handler.runDoctor({
15
+ json: options.json,
16
+ fix: options.fix,
17
+ strict: options.strict,
18
+ servicePath: options.servicePath || process.cwd()
19
+ });
20
+ if (options.json) {
21
+ console.log(JSON.stringify(results, null, 2));
22
+ } else {
23
+ displayHumanReadable(results);
24
+ }
25
+ process.exit(results.exitCode);
26
+ } catch (error) {
27
+ console.error(chalk.red('Doctor command failed:'), error.message);
28
+ process.exit(1);
29
+ }
30
+ });
31
+ }
32
+
33
+ /**
34
+ * Display results in human-readable format
35
+ */
36
+ function displayHumanReadable(results) {
37
+ console.log(chalk.cyan('๐Ÿ” Clodo Doctor - Preflight Diagnostics'));
38
+ console.log(chalk.gray(`Service: ${results.servicePath}`));
39
+ console.log(chalk.gray(`Timestamp: ${results.timestamp}`));
40
+ console.log('');
41
+
42
+ // Summary
43
+ const {
44
+ summary
45
+ } = results;
46
+ console.log(chalk.bold('Summary:'));
47
+ console.log(` Total checks: ${summary.total}`);
48
+ console.log(chalk.green(` Passed: ${summary.passed}`));
49
+ if (summary.warnings > 0) console.log(chalk.yellow(` Warnings: ${summary.warnings}`));
50
+ if (summary.errors > 0) console.log(chalk.red(` Errors: ${summary.errors}`));
51
+ if (summary.critical > 0) console.log(chalk.red.bold(` Critical: ${summary.critical}`));
52
+ console.log('');
53
+
54
+ // Show fixes applied
55
+ if (results.fixesApplied && results.fixesApplied.length > 0) {
56
+ console.log(chalk.bold('Fixes Applied:'));
57
+ results.fixesApplied.forEach(fix => {
58
+ console.log(chalk.green(` โœ… ${fix}`));
59
+ });
60
+ console.log('');
61
+ }
62
+
63
+ // Detailed results
64
+ results.checks.forEach(check => {
65
+ const icon = getStatusIcon(check.status);
66
+ const color = getStatusColor(check.status);
67
+ console.log(color(`${icon} ${check.name}`));
68
+ console.log(` ${check.message}`);
69
+ if (check.details && check.details.length > 0) {
70
+ check.details.forEach(detail => {
71
+ console.log(` ${detail}`);
72
+ });
73
+ }
74
+ if (check.fixSuggestions && check.fixSuggestions.length > 0) {
75
+ console.log(chalk.blue(' Suggestions:'));
76
+ check.fixSuggestions.forEach(suggestion => {
77
+ console.log(` โ€ข ${suggestion}`);
78
+ });
79
+ }
80
+ console.log('');
81
+ });
82
+
83
+ // Overall status
84
+ if (results.exitCode === 0) {
85
+ console.log(chalk.green('โœ… All checks passed! Service is ready for deployment.'));
86
+ } else {
87
+ console.log(chalk.red('โŒ Issues found. Address them before deployment.'));
88
+ if (results.fixSuggestions.length > 0) {
89
+ console.log(chalk.blue('Run with --fix to attempt automatic fixes.'));
90
+ }
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Get status icon
96
+ */
97
+ function getStatusIcon(status) {
98
+ switch (status) {
99
+ case 'passed':
100
+ return 'โœ…';
101
+ case 'warning':
102
+ return 'โš ๏ธ';
103
+ case 'failed':
104
+ return 'โŒ';
105
+ default:
106
+ return 'โ“';
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Get status color
112
+ */
113
+ function getStatusColor(status) {
114
+ switch (status) {
115
+ case 'passed':
116
+ return chalk.green;
117
+ case 'warning':
118
+ return chalk.yellow;
119
+ case 'failed':
120
+ return chalk.red;
121
+ default:
122
+ return chalk.gray;
123
+ }
124
+ }
@@ -0,0 +1,258 @@
1
+ /**
2
+ * Secrets Command Module
3
+ * Provides secret scanning, baseline management, and validation for Clodo services
4
+ *
5
+ * Subcommands:
6
+ * clodo-service secrets scan Scan for potential secrets in the service
7
+ * clodo-service secrets validate Validate scanned secrets against baseline
8
+ * clodo-service secrets baseline Show/update the secrets baseline
9
+ */
10
+
11
+ import { SecretsManager } from '../../src/security/SecretsManager.js';
12
+ import chalk from 'chalk';
13
+ export function registerSecretsCommand(program) {
14
+ const secrets = program.command('secrets').description('Secret scanning and baseline management for leak prevention');
15
+
16
+ // โ”€โ”€โ”€ secrets scan โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
17
+
18
+ secrets.command('scan').description('Scan for potential secrets in the service directory').option('--json', 'Output results in JSON format').option('--include-tests', 'Include test/example/placeholder values in results').option('--service-path <path>', 'Path to service directory (defaults to current directory)').option('--severity <level>', 'Minimum severity to report: critical, high, medium', 'medium').action(async options => {
19
+ try {
20
+ const mgr = new SecretsManager({
21
+ includeTests: options.includeTests
22
+ });
23
+ const servicePath = options.servicePath || process.cwd();
24
+ const findings = await mgr.scan(servicePath);
25
+
26
+ // Filter by severity
27
+ const severityOrder = {
28
+ critical: 3,
29
+ high: 2,
30
+ medium: 1
31
+ };
32
+ const minSeverity = severityOrder[options.severity] || 1;
33
+ const filtered = findings.filter(f => (severityOrder[f.severity] || 0) >= minSeverity);
34
+ if (options.json) {
35
+ console.log(JSON.stringify({
36
+ findings: filtered,
37
+ total: filtered.length,
38
+ servicePath
39
+ }, null, 2));
40
+ return;
41
+ }
42
+ displayScanResults(filtered, servicePath);
43
+ } catch (error) {
44
+ console.error(chalk.red('Secrets scan failed:'), error.message);
45
+ process.exit(1);
46
+ }
47
+ });
48
+
49
+ // โ”€โ”€โ”€ secrets validate โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
50
+
51
+ secrets.command('validate').description('Validate secrets against the baseline โ€” fails if new secrets are found').option('--json', 'Output results in JSON format').option('--strict', 'Exit with non-zero code on any findings (even baselined)').option('--service-path <path>', 'Path to service directory (defaults to current directory)').action(async options => {
52
+ try {
53
+ const mgr = new SecretsManager();
54
+ const servicePath = options.servicePath || process.cwd();
55
+ const result = await mgr.validate(servicePath);
56
+ if (options.json) {
57
+ console.log(JSON.stringify(result, null, 2));
58
+ } else {
59
+ displayValidationResults(result, servicePath);
60
+ }
61
+ const exitCode = result.passed ? 0 : 1;
62
+ if (options.strict && result.totalFindings > 0) {
63
+ process.exit(1);
64
+ }
65
+ process.exit(exitCode);
66
+ } catch (error) {
67
+ console.error(chalk.red('Secrets validation failed:'), error.message);
68
+ process.exit(1);
69
+ }
70
+ });
71
+
72
+ // โ”€โ”€โ”€ secrets baseline โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
73
+
74
+ const baseline = secrets.command('baseline').description('Manage the .secrets.baseline file');
75
+
76
+ // โ”€โ”€โ”€ secrets baseline show โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
77
+
78
+ baseline.command('show').description('Display current baseline entries').option('--json', 'Output results in JSON format').option('--service-path <path>', 'Path to service directory (defaults to current directory)').action(async options => {
79
+ try {
80
+ const mgr = new SecretsManager();
81
+ const servicePath = options.servicePath || process.cwd();
82
+ const entries = await mgr.baselineShow(servicePath);
83
+ if (options.json) {
84
+ console.log(JSON.stringify(entries, null, 2));
85
+ return;
86
+ }
87
+ displayBaselineEntries(entries, servicePath);
88
+ } catch (error) {
89
+ console.error(chalk.red('Failed to show baseline:'), error.message);
90
+ process.exit(1);
91
+ }
92
+ });
93
+
94
+ // โ”€โ”€โ”€ secrets baseline update โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
95
+
96
+ baseline.command('update').description('Update baseline with current scan findings').option('--json', 'Output results in JSON format').option('--add-all', 'Add all new findings to the baseline').option('--prune', 'Remove stale entries no longer detected').option('--reason <reason>', 'Reason for updating (audit trail)').option('--service-path <path>', 'Path to service directory (defaults to current directory)').action(async options => {
97
+ try {
98
+ const mgr = new SecretsManager();
99
+ const servicePath = options.servicePath || process.cwd();
100
+ const result = await mgr.baselineUpdate(servicePath, {
101
+ addAll: options.addAll,
102
+ prune: options.prune,
103
+ reason: options.reason
104
+ });
105
+ if (options.json) {
106
+ console.log(JSON.stringify(result, null, 2));
107
+ return;
108
+ }
109
+ displayBaselineUpdateResults(result);
110
+ } catch (error) {
111
+ console.error(chalk.red('Baseline update failed:'), error.message);
112
+ process.exit(1);
113
+ }
114
+ });
115
+
116
+ // โ”€โ”€โ”€ secrets patterns โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
117
+
118
+ secrets.command('patterns').description('List configured detection patterns and their severity').option('--json', 'Output results in JSON format').action(options => {
119
+ const mgr = new SecretsManager();
120
+ const patterns = mgr.getPatterns();
121
+ if (options.json) {
122
+ console.log(JSON.stringify(patterns, null, 2));
123
+ return;
124
+ }
125
+ console.log(chalk.cyan('๐Ÿ”‘ Secret Detection Patterns'));
126
+ console.log('');
127
+ const grouped = {};
128
+ for (const p of patterns) {
129
+ if (!grouped[p.severity]) grouped[p.severity] = [];
130
+ grouped[p.severity].push(p.name);
131
+ }
132
+ for (const severity of ['critical', 'high', 'medium']) {
133
+ if (grouped[severity]) {
134
+ const color = severity === 'critical' ? chalk.red : severity === 'high' ? chalk.yellow : chalk.blue;
135
+ console.log(color(` [${severity.toUpperCase()}]`));
136
+ grouped[severity].forEach(name => {
137
+ console.log(` โ€ข ${name}`);
138
+ });
139
+ console.log('');
140
+ }
141
+ }
142
+ });
143
+ }
144
+
145
+ // โ”€โ”€โ”€ Display Helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
146
+
147
+ function displayScanResults(findings, servicePath) {
148
+ console.log(chalk.cyan('๐Ÿ”‘ Clodo Secrets Scan'));
149
+ console.log(chalk.gray(`Service: ${servicePath}`));
150
+ console.log('');
151
+ if (findings.length === 0) {
152
+ console.log(chalk.green('โœ… No potential secrets found. Code looks clean!'));
153
+ return;
154
+ }
155
+ console.log(chalk.yellow(`โš ๏ธ Found ${findings.length} potential secret(s):`));
156
+ console.log('');
157
+
158
+ // Group by severity
159
+ const bySeverity = {
160
+ critical: [],
161
+ high: [],
162
+ medium: []
163
+ };
164
+ findings.forEach(f => {
165
+ if (bySeverity[f.severity]) bySeverity[f.severity].push(f);
166
+ });
167
+ for (const severity of ['critical', 'high', 'medium']) {
168
+ const group = bySeverity[severity];
169
+ if (group.length === 0) continue;
170
+ const color = severity === 'critical' ? chalk.red.bold : severity === 'high' ? chalk.yellow : chalk.blue;
171
+ console.log(color(` โ”€โ”€ ${severity.toUpperCase()} (${group.length}) โ”€โ”€`));
172
+ group.forEach(f => {
173
+ console.log(` ${chalk.gray(f.file)}:${chalk.white(f.line)} [${f.pattern}]`);
174
+ console.log(` ${chalk.dim(f.match)}`);
175
+ });
176
+ console.log('');
177
+ }
178
+ console.log(chalk.blue('๐Ÿ’ก Run `clodo-service secrets baseline update --add-all` to baseline known findings'));
179
+ console.log(chalk.blue(' Run `clodo-service secrets validate` to check against baseline'));
180
+ }
181
+ function displayValidationResults(result, servicePath) {
182
+ console.log(chalk.cyan('๐Ÿ”‘ Clodo Secrets Validation'));
183
+ console.log(chalk.gray(`Service: ${servicePath}`));
184
+ console.log('');
185
+ if (result.passed) {
186
+ console.log(chalk.green(`โœ… ${result.message}`));
187
+ if (result.baselineCount > 0) {
188
+ console.log(chalk.gray(` (${result.baselineCount} known entries in baseline)`));
189
+ }
190
+ } else {
191
+ console.log(chalk.red(`โŒ ${result.message}`));
192
+ console.log('');
193
+ result.newFindings.forEach(f => {
194
+ const color = f.severity === 'critical' ? chalk.red : chalk.yellow;
195
+ console.log(color(` ${f.file}:${f.line} [${f.pattern}] (${f.severity})`));
196
+ console.log(` ${chalk.dim(f.match)}`);
197
+ });
198
+ console.log('');
199
+ console.log(chalk.blue('๐Ÿ’ก Fix the secrets above, or run:'));
200
+ console.log(chalk.blue(' clodo-service secrets baseline update --add-all --reason "reviewed-safe"'));
201
+ }
202
+ if (result.removedFromBaseline.length > 0) {
203
+ console.log('');
204
+ console.log(chalk.gray(`โ„น๏ธ ${result.removedFromBaseline.length} baseline entries are stale (no longer detected).`));
205
+ console.log(chalk.gray(' Run `clodo-service secrets baseline update --prune` to clean them up.'));
206
+ }
207
+ }
208
+ function displayBaselineEntries(entries, servicePath) {
209
+ console.log(chalk.cyan('๐Ÿ”‘ Secrets Baseline'));
210
+ console.log(chalk.gray(`Service: ${servicePath}`));
211
+ console.log('');
212
+ if (entries.length === 0) {
213
+ console.log(chalk.gray(' No baseline entries. Run `clodo-service secrets scan` to find potential secrets.'));
214
+ return;
215
+ }
216
+ console.log(` Total entries: ${entries.length}`);
217
+ console.log('');
218
+
219
+ // Group by file
220
+ const byFile = {};
221
+ entries.forEach(e => {
222
+ if (!byFile[e.file]) byFile[e.file] = [];
223
+ byFile[e.file].push(e);
224
+ });
225
+ Object.entries(byFile).forEach(([file, fileEntries]) => {
226
+ console.log(chalk.white(` ${file}`));
227
+ fileEntries.forEach(e => {
228
+ const info = e.addedAt ? chalk.dim(` (added: ${e.addedAt.split('T')[0]})`) : '';
229
+ const reason = e.reason ? chalk.dim(` [${e.reason}]`) : '';
230
+ console.log(` L${e.line}: [${e.pattern}] ${chalk.dim(e.match)}${info}${reason}`);
231
+ });
232
+ });
233
+ }
234
+ function displayBaselineUpdateResults(result) {
235
+ console.log(chalk.cyan('๐Ÿ”‘ Baseline Update'));
236
+ console.log('');
237
+ if (result.added > 0) {
238
+ console.log(chalk.green(` โœ… Added ${result.added} new entries`));
239
+ }
240
+ if (result.pruned > 0) {
241
+ console.log(chalk.yellow(` ๐Ÿงน Pruned ${result.pruned} stale entries`));
242
+ }
243
+ if (result.added === 0 && result.pruned === 0) {
244
+ if (result.newFindings === 0 && result.staleEntries === 0) {
245
+ console.log(chalk.green(' โœ… Baseline is already up to date'));
246
+ } else {
247
+ if (result.newFindings > 0) {
248
+ console.log(chalk.yellow(` โš ๏ธ ${result.newFindings} new findings detected but --add-all not specified`));
249
+ }
250
+ if (result.staleEntries > 0) {
251
+ console.log(chalk.gray(` โ„น๏ธ ${result.staleEntries} stale entries detected but --prune not specified`));
252
+ }
253
+ }
254
+ }
255
+ console.log('');
256
+ console.log(chalk.gray(` Total baseline entries: ${result.total}`));
257
+ console.log(chalk.gray(` Baseline path: ${result.baselinePath}`));
258
+ }
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { SecurityCLI } from '../security/SecurityCLI.js';
2
+ import { SecurityCLI } from '../src/security/SecurityCLI.js';
3
3
  const command = process.argv[2];
4
4
  const args = process.argv.slice(3);
5
5
  async function main() {
package/dist/index.js CHANGED
@@ -58,6 +58,8 @@ export { deployServiceProgrammatic } from './programmatic/deployService.js';
58
58
  export { validateServiceProgrammatic } from './programmatic/validateService.js';
59
59
  export { getFrameworkCapabilities, getFrameworkVersion } from './api/frameworkCapabilities.js';
60
60
  export { getAcceptedParameters, validateServicePayload } from './validation/payloadValidation.js';
61
+ export { ConfigSchemaValidator } from './validation/ConfigSchemaValidator.js';
62
+ export { getConfigSchema, getRegisteredConfigTypes } from './validation/configSchemas.js';
61
63
  export { IntegrationError, PayloadValidationError, ParameterNotSupportedError, VersionCompatibilityError } from './errors/integrationErrors.js';
62
64
  export { MockServiceOrchestrator, createMockFramework } from './testing/mockFramework.js';
63
65
 
@@ -74,7 +76,7 @@ export { classifyError, getRecoverySuggestions } from './lib/shared/error-handli
74
76
  export { FrameworkInfo } from './version/FrameworkInfo.js';
75
77
  export { TemplateRuntime } from './utils/TemplateRuntime.js';
76
78
  export { HealthChecker } from './monitoring/HealthChecker.js';
77
- export const FRAMEWORK_VERSION = '4.4.1';
79
+ export const FRAMEWORK_VERSION = '4.5.1';
78
80
  export const FRAMEWORK_NAME = 'Clodo Framework';
79
81
 
80
82
  // โ”€โ”€โ”€ Compatibility Constants (for consistent wrangler.toml generation) โ”€โ”€