sumulige-claude 1.1.0 → 1.1.1
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/.claude/hooks/pre-commit.cjs +86 -0
- package/.claude/hooks/pre-push.cjs +103 -0
- package/.claude/quality-gate.json +61 -0
- package/.claude/settings.local.json +2 -1
- package/cli.js +28 -0
- package/config/quality-gate.json +61 -0
- package/lib/commands.js +208 -0
- package/lib/config-manager.js +441 -0
- package/lib/config-schema.js +408 -0
- package/lib/config-validator.js +330 -0
- package/lib/config.js +52 -1
- package/lib/errors.js +305 -0
- package/lib/quality-gate.js +431 -0
- package/lib/quality-rules.js +373 -0
- package/package.json +5 -1
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Pre-commit Quality Gate
|
|
4
|
+
*
|
|
5
|
+
* Runs quality checks before committing.
|
|
6
|
+
* Fails the commit if critical or error issues are found.
|
|
7
|
+
*
|
|
8
|
+
* Install: ln -s ../../.claude/hooks/pre-commit.cjs .git/hooks/pre-commit
|
|
9
|
+
* Or use: smc hooks install
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { execSync } = require('child_process');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
|
|
16
|
+
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
17
|
+
|
|
18
|
+
async function main() {
|
|
19
|
+
// Check if quality gate is enabled
|
|
20
|
+
const configPath = path.join(projectDir, '.claude', 'quality-gate.json');
|
|
21
|
+
let config = { enabled: true, gates: { preCommit: true } };
|
|
22
|
+
|
|
23
|
+
if (fs.existsSync(configPath)) {
|
|
24
|
+
try {
|
|
25
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
26
|
+
} catch {}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!config.enabled || !config.gates?.preCommit) {
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Get staged files
|
|
34
|
+
let stagedFiles = [];
|
|
35
|
+
try {
|
|
36
|
+
const output = execSync('git diff --cached --name-only --diff-filter=ACM', {
|
|
37
|
+
encoding: 'utf-8',
|
|
38
|
+
stdio: 'pipe'
|
|
39
|
+
});
|
|
40
|
+
stagedFiles = output.trim().split('\n').filter(Boolean);
|
|
41
|
+
} catch {
|
|
42
|
+
// Not in git repo or no staged files
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (stagedFiles.length === 0) {
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Filter to checkable files
|
|
51
|
+
const checkable = stagedFiles.filter(f => {
|
|
52
|
+
const ext = path.extname(f);
|
|
53
|
+
return ['.js', '.ts', '.jsx', '.tsx', '.cjs', '.mjs', '.json', '.md'].includes(ext);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (checkable.length === 0) {
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log(`Running pre-commit quality checks on ${checkable.length} file(s)...`);
|
|
61
|
+
|
|
62
|
+
// Run quality gate
|
|
63
|
+
const { QualityGate } = require(path.join(__dirname, '..', '..', 'lib', 'quality-gate.js'));
|
|
64
|
+
const gate = new QualityGate({
|
|
65
|
+
projectDir,
|
|
66
|
+
config
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const result = await gate.check({
|
|
70
|
+
files: checkable.map(f => path.join(projectDir, f)),
|
|
71
|
+
severity: 'error' // Block on errors and critical only
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
if (!result.passed) {
|
|
75
|
+
console.error('\nPre-commit quality gate failed.');
|
|
76
|
+
console.error('Fix issues or use --no-verify to bypass (not recommended).\n');
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.log('Pre-commit quality checks passed.\n');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
main().catch(err => {
|
|
84
|
+
console.error('Pre-commit hook error:', err.message);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Pre-push Quality Gate
|
|
4
|
+
*
|
|
5
|
+
* Runs full quality checks before pushing.
|
|
6
|
+
* More comprehensive than pre-commit.
|
|
7
|
+
*
|
|
8
|
+
* Install: ln -s ../../.claude/hooks/pre-push.cjs .git/hooks/pre-push
|
|
9
|
+
* Or use: smc hooks install
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { execSync } = require('child_process');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
|
|
16
|
+
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
17
|
+
|
|
18
|
+
async function main() {
|
|
19
|
+
// Check if quality gate is enabled
|
|
20
|
+
const configPath = path.join(projectDir, '.claude', 'quality-gate.json');
|
|
21
|
+
let config = { enabled: true, gates: { prePush: true } };
|
|
22
|
+
|
|
23
|
+
if (fs.existsSync(configPath)) {
|
|
24
|
+
try {
|
|
25
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
26
|
+
} catch {}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!config.enabled || !config.gates?.prePush) {
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log('Running pre-push quality checks...\n');
|
|
34
|
+
|
|
35
|
+
// Get all files that will be pushed
|
|
36
|
+
let filesToCheck = [];
|
|
37
|
+
try {
|
|
38
|
+
// Get files changed since last push
|
|
39
|
+
const upstream = execSync('git rev-parse --abbrev-ref --symbolic-full-name @{u}', {
|
|
40
|
+
encoding: 'utf-8',
|
|
41
|
+
stdio: 'pipe'
|
|
42
|
+
}).trim() || 'origin/main';
|
|
43
|
+
|
|
44
|
+
const output = execSync(`git diff --name-only ${upstream}...HEAD`, {
|
|
45
|
+
encoding: 'utf-8',
|
|
46
|
+
stdio: 'pipe'
|
|
47
|
+
});
|
|
48
|
+
filesToCheck = output.trim().split('\n').filter(Boolean);
|
|
49
|
+
} catch {
|
|
50
|
+
// No upstream or other git error - check staged files only
|
|
51
|
+
try {
|
|
52
|
+
const output = execSync('git diff --cached --name-only --diff-filter=ACM', {
|
|
53
|
+
encoding: 'utf-8',
|
|
54
|
+
stdio: 'pipe'
|
|
55
|
+
});
|
|
56
|
+
filesToCheck = output.trim().split('\n').filter(Boolean);
|
|
57
|
+
} catch {
|
|
58
|
+
// Not in git repo
|
|
59
|
+
process.exit(0);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (filesToCheck.length === 0) {
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Filter to checkable files
|
|
68
|
+
const checkable = filesToCheck.filter(f => {
|
|
69
|
+
const ext = path.extname(f);
|
|
70
|
+
return ['.js', '.ts', '.jsx', '.tsx', '.cjs', '.mjs', '.json', '.md'].includes(ext);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (checkable.length === 0) {
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log(`Checking ${checkable.length} changed file(s)...\n`);
|
|
78
|
+
|
|
79
|
+
// Run quality gate
|
|
80
|
+
const { QualityGate } = require(path.join(__dirname, '..', '..', 'lib', 'quality-gate.js'));
|
|
81
|
+
const gate = new QualityGate({
|
|
82
|
+
projectDir,
|
|
83
|
+
config
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const result = await gate.check({
|
|
87
|
+
files: checkable.map(f => path.join(projectDir, f)),
|
|
88
|
+
severity: 'warn' // Block on warnings too for push
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (!result.passed) {
|
|
92
|
+
console.error('\nPush blocked by quality gate.');
|
|
93
|
+
console.error('Fix issues or use --no-verify to bypass (not recommended).\n');
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.log('All quality checks passed. Proceeding with push.\n');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
main().catch(err => {
|
|
101
|
+
console.error('Pre-push hook error:', err.message);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"enabled": true,
|
|
3
|
+
"severity": "warn",
|
|
4
|
+
"rules": [
|
|
5
|
+
{
|
|
6
|
+
"id": "line-count-limit",
|
|
7
|
+
"enabled": true,
|
|
8
|
+
"severity": "error",
|
|
9
|
+
"config": {
|
|
10
|
+
"maxLines": 800
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"id": "file-size-limit",
|
|
15
|
+
"enabled": true,
|
|
16
|
+
"severity": "warn",
|
|
17
|
+
"config": {
|
|
18
|
+
"maxSize": 819200
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "no-empty-files",
|
|
23
|
+
"enabled": true,
|
|
24
|
+
"severity": "warn"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "no-trailing-whitespace",
|
|
28
|
+
"enabled": true,
|
|
29
|
+
"severity": "warn"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "todo-comments",
|
|
33
|
+
"enabled": true,
|
|
34
|
+
"severity": "info"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "directory-depth",
|
|
38
|
+
"enabled": false,
|
|
39
|
+
"severity": "warn"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "no-console-logs",
|
|
43
|
+
"enabled": false,
|
|
44
|
+
"severity": "warn"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"id": "function-length",
|
|
48
|
+
"enabled": false,
|
|
49
|
+
"severity": "warn"
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"gates": {
|
|
53
|
+
"preCommit": true,
|
|
54
|
+
"prePush": true,
|
|
55
|
+
"onToolUse": false
|
|
56
|
+
},
|
|
57
|
+
"reporting": {
|
|
58
|
+
"format": "console",
|
|
59
|
+
"outputFile": null
|
|
60
|
+
}
|
|
61
|
+
}
|
package/cli.js
CHANGED
|
@@ -132,6 +132,34 @@ const COMMANDS = {
|
|
|
132
132
|
help: 'Manage configuration',
|
|
133
133
|
args: '[get|set] [key] [value]'
|
|
134
134
|
},
|
|
135
|
+
'config:validate': {
|
|
136
|
+
help: 'Validate configuration files',
|
|
137
|
+
args: ''
|
|
138
|
+
},
|
|
139
|
+
'config:backup': {
|
|
140
|
+
help: 'Create configuration backup',
|
|
141
|
+
args: ''
|
|
142
|
+
},
|
|
143
|
+
'config:rollback': {
|
|
144
|
+
help: 'Rollback to previous config',
|
|
145
|
+
args: '[version]'
|
|
146
|
+
},
|
|
147
|
+
'config:diff': {
|
|
148
|
+
help: 'Show config diff',
|
|
149
|
+
args: '[file1] [file2]'
|
|
150
|
+
},
|
|
151
|
+
'qg:check': {
|
|
152
|
+
help: 'Run quality gate check',
|
|
153
|
+
args: '[severity]'
|
|
154
|
+
},
|
|
155
|
+
'qg:rules': {
|
|
156
|
+
help: 'List available quality rules',
|
|
157
|
+
args: ''
|
|
158
|
+
},
|
|
159
|
+
'qg:init': {
|
|
160
|
+
help: 'Initialize quality gate config',
|
|
161
|
+
args: ''
|
|
162
|
+
},
|
|
135
163
|
changelog: {
|
|
136
164
|
help: 'Generate changelog from git commits',
|
|
137
165
|
args: '[--from <tag>] [--to <tag>] [--json]'
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"enabled": true,
|
|
3
|
+
"severity": "warn",
|
|
4
|
+
"rules": [
|
|
5
|
+
{
|
|
6
|
+
"id": "line-count-limit",
|
|
7
|
+
"enabled": true,
|
|
8
|
+
"severity": "error",
|
|
9
|
+
"config": {
|
|
10
|
+
"maxLines": 800
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"id": "file-size-limit",
|
|
15
|
+
"enabled": true,
|
|
16
|
+
"severity": "warn",
|
|
17
|
+
"config": {
|
|
18
|
+
"maxSize": 819200
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "no-empty-files",
|
|
23
|
+
"enabled": true,
|
|
24
|
+
"severity": "warn"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "no-trailing-whitespace",
|
|
28
|
+
"enabled": true,
|
|
29
|
+
"severity": "warn"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "todo-comments",
|
|
33
|
+
"enabled": true,
|
|
34
|
+
"severity": "info"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "directory-depth",
|
|
38
|
+
"enabled": false,
|
|
39
|
+
"severity": "warn"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "no-console-logs",
|
|
43
|
+
"enabled": false,
|
|
44
|
+
"severity": "warn"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"id": "function-length",
|
|
48
|
+
"enabled": false,
|
|
49
|
+
"severity": "warn"
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"gates": {
|
|
53
|
+
"preCommit": true,
|
|
54
|
+
"prePush": true,
|
|
55
|
+
"onToolUse": false
|
|
56
|
+
},
|
|
57
|
+
"reporting": {
|
|
58
|
+
"format": "console",
|
|
59
|
+
"outputFile": null
|
|
60
|
+
}
|
|
61
|
+
}
|
package/lib/commands.js
CHANGED
|
@@ -2321,6 +2321,214 @@ All notable changes to this project will be documented in this file.
|
|
|
2321
2321
|
|
|
2322
2322
|
log('=====================================', 'gray');
|
|
2323
2323
|
log('', 'gray');
|
|
2324
|
+
},
|
|
2325
|
+
|
|
2326
|
+
// ==========================================================================
|
|
2327
|
+
// Quality Gate Commands
|
|
2328
|
+
// ==========================================================================
|
|
2329
|
+
|
|
2330
|
+
'qg:check': async (severity = 'warn') => {
|
|
2331
|
+
const { QualityGate } = require('./quality-gate');
|
|
2332
|
+
const gate = new QualityGate({ projectDir: process.cwd() });
|
|
2333
|
+
const result = await gate.check({ severity });
|
|
2334
|
+
process.exit(result.passed ? 0 : 1);
|
|
2335
|
+
},
|
|
2336
|
+
|
|
2337
|
+
'qg:rules': () => {
|
|
2338
|
+
const registry = require('./quality-rules').registry;
|
|
2339
|
+
const rules = registry.getAll();
|
|
2340
|
+
|
|
2341
|
+
console.log('📋 Available Quality Rules');
|
|
2342
|
+
console.log('');
|
|
2343
|
+
console.log('Rules are checked when running quality gate:');
|
|
2344
|
+
console.log('');
|
|
2345
|
+
|
|
2346
|
+
const byCategory = {};
|
|
2347
|
+
for (const rule of rules) {
|
|
2348
|
+
if (!byCategory[rule.category]) byCategory[rule.category] = [];
|
|
2349
|
+
byCategory[rule.category].push(rule);
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
for (const [category, rules] of Object.entries(byCategory)) {
|
|
2353
|
+
console.log(`${category.toUpperCase()}:`);
|
|
2354
|
+
for (const rule of rules) {
|
|
2355
|
+
const status = rule.enabled ? '✅' : '⊝';
|
|
2356
|
+
const sev = { info: 'I', warn: 'W', error: 'E', critical: 'X' }[rule.severity];
|
|
2357
|
+
console.log(` ${status} ${rule.id} [${sev}] - ${rule.name}`);
|
|
2358
|
+
}
|
|
2359
|
+
console.log('');
|
|
2360
|
+
}
|
|
2361
|
+
},
|
|
2362
|
+
|
|
2363
|
+
'qg:init': () => {
|
|
2364
|
+
const projectDir = process.cwd();
|
|
2365
|
+
const configDir = path.join(projectDir, '.claude');
|
|
2366
|
+
const targetPath = path.join(configDir, 'quality-gate.json');
|
|
2367
|
+
const sourcePath = path.join(__dirname, '../config/quality-gate.json');
|
|
2368
|
+
|
|
2369
|
+
if (fs.existsSync(targetPath)) {
|
|
2370
|
+
console.log('⚠️ quality-gate.json already exists');
|
|
2371
|
+
return;
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
ensureDir(configDir);
|
|
2375
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
2376
|
+
console.log('✅ Created .claude/quality-gate.json');
|
|
2377
|
+
console.log('');
|
|
2378
|
+
console.log('To enable Git hooks:');
|
|
2379
|
+
console.log(' ln -s .claude/hooks/pre-commit.cjs .git/hooks/pre-commit');
|
|
2380
|
+
console.log(' ln -s .claude/hooks/pre-push.cjs .git/hooks/pre-push');
|
|
2381
|
+
},
|
|
2382
|
+
|
|
2383
|
+
// ==========================================================================
|
|
2384
|
+
// Config Commands
|
|
2385
|
+
// ==========================================================================
|
|
2386
|
+
|
|
2387
|
+
'config:validate': () => {
|
|
2388
|
+
const { ConfigValidator } = require('./config-validator');
|
|
2389
|
+
const validator = new ConfigValidator();
|
|
2390
|
+
|
|
2391
|
+
console.log('🔍 Validating configuration...');
|
|
2392
|
+
console.log('');
|
|
2393
|
+
|
|
2394
|
+
let hasErrors = false;
|
|
2395
|
+
let hasWarnings = false;
|
|
2396
|
+
|
|
2397
|
+
// Check global config
|
|
2398
|
+
const globalConfigPath = path.join(process.env.HOME, '.claude', 'config.json');
|
|
2399
|
+
console.log(`Global: ${globalConfigPath}`);
|
|
2400
|
+
const globalResult = validator.validateFile(globalConfigPath);
|
|
2401
|
+
|
|
2402
|
+
if (globalResult.valid) {
|
|
2403
|
+
console.log(' ✅ Valid');
|
|
2404
|
+
} else {
|
|
2405
|
+
if (globalResult.errors.length > 0) {
|
|
2406
|
+
hasErrors = true;
|
|
2407
|
+
console.log(` ❌ ${globalResult.errors.length} error(s)`);
|
|
2408
|
+
globalResult.errors.forEach(e => {
|
|
2409
|
+
console.log(` [${e.severity}] ${e.path}: ${e.message}`);
|
|
2410
|
+
});
|
|
2411
|
+
}
|
|
2412
|
+
if (globalResult.warnings.length > 0) {
|
|
2413
|
+
hasWarnings = true;
|
|
2414
|
+
console.log(` ⚠️ ${globalResult.warnings.length} warning(s)`);
|
|
2415
|
+
globalResult.warnings.forEach(e => {
|
|
2416
|
+
console.log(` [${e.severity}] ${e.path}: ${e.message}`);
|
|
2417
|
+
});
|
|
2418
|
+
}
|
|
2419
|
+
if (globalResult.errors.length === 0 && globalResult.warnings.length === 0) {
|
|
2420
|
+
console.log(' ❌ Validation failed');
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
console.log('');
|
|
2424
|
+
|
|
2425
|
+
// Check project config if in project
|
|
2426
|
+
const projectDir = process.cwd();
|
|
2427
|
+
const projectConfigPath = path.join(projectDir, '.claude', 'settings.json');
|
|
2428
|
+
if (fs.existsSync(projectConfigPath)) {
|
|
2429
|
+
console.log(`Project: ${projectConfigPath}`);
|
|
2430
|
+
const projectResult = validator.validateFile(projectConfigPath, 'settings');
|
|
2431
|
+
if (projectResult.valid) {
|
|
2432
|
+
console.log(' ✅ Valid');
|
|
2433
|
+
} else {
|
|
2434
|
+
if (projectResult.errors.length > 0) {
|
|
2435
|
+
hasErrors = true;
|
|
2436
|
+
console.log(` ❌ ${projectResult.errors.length} error(s)`);
|
|
2437
|
+
projectResult.errors.forEach(e => {
|
|
2438
|
+
console.log(` [${e.severity}] ${e.path}: ${e.message}`);
|
|
2439
|
+
});
|
|
2440
|
+
}
|
|
2441
|
+
if (projectResult.warnings.length > 0) {
|
|
2442
|
+
hasWarnings = true;
|
|
2443
|
+
console.log(` ⚠️ ${projectResult.warnings.length} warning(s)`);
|
|
2444
|
+
projectResult.warnings.forEach(e => {
|
|
2445
|
+
console.log(` [${e.severity}] ${e.path}: ${e.message}`);
|
|
2446
|
+
});
|
|
2447
|
+
}
|
|
2448
|
+
if (projectResult.errors.length === 0 && projectResult.warnings.length === 0) {
|
|
2449
|
+
console.log(' ❌ Validation failed');
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
console.log('');
|
|
2454
|
+
|
|
2455
|
+
if (hasErrors) {
|
|
2456
|
+
console.log('❌ Configuration validation failed');
|
|
2457
|
+
process.exit(1);
|
|
2458
|
+
} else if (hasWarnings) {
|
|
2459
|
+
console.log('⚠️ Configuration has warnings (but is valid)');
|
|
2460
|
+
} else {
|
|
2461
|
+
console.log('✅ All configurations are valid');
|
|
2462
|
+
}
|
|
2463
|
+
},
|
|
2464
|
+
|
|
2465
|
+
'config:backup': () => {
|
|
2466
|
+
const { ConfigManager } = require('./config-manager');
|
|
2467
|
+
const manager = new ConfigManager();
|
|
2468
|
+
const backupPath = manager._createBackup();
|
|
2469
|
+
console.log('✅ Config backed up to:', backupPath);
|
|
2470
|
+
},
|
|
2471
|
+
|
|
2472
|
+
'config:rollback': (version) => {
|
|
2473
|
+
const { ConfigManager } = require('./config-manager');
|
|
2474
|
+
const manager = new ConfigManager();
|
|
2475
|
+
|
|
2476
|
+
const backups = manager.listBackups();
|
|
2477
|
+
if (backups.length === 0) {
|
|
2478
|
+
console.log('❌ No backups available');
|
|
2479
|
+
return;
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
if (!version) {
|
|
2483
|
+
console.log('Available backups:');
|
|
2484
|
+
backups.forEach((b, i) => {
|
|
2485
|
+
console.log(` ${i + 1}. ${b.version} (${new Date(b.timestamp).toLocaleString()})`);
|
|
2486
|
+
});
|
|
2487
|
+
console.log('');
|
|
2488
|
+
console.log('Usage: smc config:rollback <version>');
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
try {
|
|
2493
|
+
const result = manager.rollback(version);
|
|
2494
|
+
console.log('✅ Rolled back to:', result.restoredFrom);
|
|
2495
|
+
} catch (e) {
|
|
2496
|
+
console.log('❌', e.message);
|
|
2497
|
+
}
|
|
2498
|
+
},
|
|
2499
|
+
|
|
2500
|
+
'config:diff': (file1, file2) => {
|
|
2501
|
+
const { ConfigManager } = require('./config-manager');
|
|
2502
|
+
const manager = new ConfigManager();
|
|
2503
|
+
|
|
2504
|
+
if (!file1) {
|
|
2505
|
+
const backups = manager.listBackups();
|
|
2506
|
+
if (backups.length === 0) {
|
|
2507
|
+
console.log('❌ No backups available');
|
|
2508
|
+
return;
|
|
2509
|
+
}
|
|
2510
|
+
file1 = path.join(manager.backupDir, backups[0].file);
|
|
2511
|
+
file2 = null; // Current config
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
const changes = manager.diff(file1, file2);
|
|
2515
|
+
if (changes.length === 0) {
|
|
2516
|
+
console.log('✅ No differences found');
|
|
2517
|
+
return;
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
console.log('📊 Config Diff:');
|
|
2521
|
+
console.log('');
|
|
2522
|
+
for (const change of changes) {
|
|
2523
|
+
const icon = { added: '+', removed: '-', changed: '~' }[change.type];
|
|
2524
|
+
console.log(` ${icon} ${change.path}`);
|
|
2525
|
+
if (change.type !== 'removed') {
|
|
2526
|
+
console.log(` from: ${JSON.stringify(change.from)}`);
|
|
2527
|
+
}
|
|
2528
|
+
if (change.type !== 'added') {
|
|
2529
|
+
console.log(` to: ${JSON.stringify(change.to)}`);
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2324
2532
|
}
|
|
2325
2533
|
};
|
|
2326
2534
|
|