cerber-core 1.1.2 → 1.1.3
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/dev/templates/BACKEND_SCHEMA.ts.tpl +4 -2
- package/dev/templates/cerber-guardian.mjs.tpl +106 -2
- package/package.json +1 -1
- package/solo/templates/BACKEND_SCHEMA.ts.tpl +4 -2
- package/solo/templates/cerber-guardian.mjs.tpl +106 -2
- package/team/templates/BACKEND_SCHEMA.ts.tpl +4 -2
- package/team/templates/cerber-guardian.mjs.tpl +106 -2
|
@@ -20,12 +20,14 @@ export const BACKEND_SCHEMA = {
|
|
|
20
20
|
forbiddenPatterns: [
|
|
21
21
|
// Uncomment and customize based on your tech stack:
|
|
22
22
|
// {
|
|
23
|
-
// pattern:
|
|
23
|
+
// pattern: "password\\s*=\\s*['\"][^'\"]+['\"]",
|
|
24
|
+
// flags: "i",
|
|
24
25
|
// name: "Hardcoded passwords",
|
|
25
26
|
// severity: "error"
|
|
26
27
|
// },
|
|
27
28
|
// {
|
|
28
|
-
// pattern:
|
|
29
|
+
// pattern: "api[_-]?key\\s*=\\s*['\"][^'\"]+['\"]",
|
|
30
|
+
// flags: "i",
|
|
29
31
|
// name: "Hardcoded API keys",
|
|
30
32
|
// severity: "error"
|
|
31
33
|
// }
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { execSync } from 'child_process';
|
|
6
6
|
import fs from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
7
8
|
|
|
8
9
|
const SCHEMA_FILE = '{{SCHEMA_FILE}}';
|
|
9
10
|
const APPROVALS_TAG = '{{APPROVALS_TAG}}';
|
|
@@ -13,10 +14,23 @@ async function main() {
|
|
|
13
14
|
|
|
14
15
|
if (!fs.existsSync(SCHEMA_FILE)) {
|
|
15
16
|
console.error(`❌ Schema file not found: ${SCHEMA_FILE}`);
|
|
16
|
-
console.error('
|
|
17
|
+
console.error('Create your schema file to enable validation.');
|
|
18
|
+
console.error(`Example: npx cerber init --print-schema-template > ${SCHEMA_FILE}`);
|
|
17
19
|
process.exit(1);
|
|
18
20
|
}
|
|
19
21
|
|
|
22
|
+
// Import schema
|
|
23
|
+
let schema;
|
|
24
|
+
try {
|
|
25
|
+
const schemaPath = join(process.cwd(), SCHEMA_FILE);
|
|
26
|
+
const schemaModule = await import(`file://${schemaPath}`);
|
|
27
|
+
schema = schemaModule.BACKEND_SCHEMA || schemaModule.default || schemaModule;
|
|
28
|
+
} catch (err) {
|
|
29
|
+
console.error(`❌ Failed to load schema from ${SCHEMA_FILE}:`, err.message);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Get staged files
|
|
20
34
|
let stagedFiles;
|
|
21
35
|
try {
|
|
22
36
|
stagedFiles = execSync('git diff --cached --name-only', { encoding: 'utf-8' })
|
|
@@ -34,7 +48,97 @@ async function main() {
|
|
|
34
48
|
}
|
|
35
49
|
|
|
36
50
|
console.log(`Checking ${stagedFiles.length} file(s)...`);
|
|
37
|
-
|
|
51
|
+
|
|
52
|
+
const violations = [];
|
|
53
|
+
|
|
54
|
+
// Validate each staged file
|
|
55
|
+
for (const file of stagedFiles) {
|
|
56
|
+
if (!fs.existsSync(file)) continue;
|
|
57
|
+
|
|
58
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
59
|
+
|
|
60
|
+
// Check forbidden patterns
|
|
61
|
+
if (schema.forbiddenPatterns && Array.isArray(schema.forbiddenPatterns)) {
|
|
62
|
+
for (const rule of schema.forbiddenPatterns) {
|
|
63
|
+
if (!rule.pattern) continue;
|
|
64
|
+
|
|
65
|
+
const pattern = typeof rule.pattern === 'string'
|
|
66
|
+
? new RegExp(rule.pattern, rule.flags || 'i')
|
|
67
|
+
: rule.pattern;
|
|
68
|
+
|
|
69
|
+
// Check if file is in exceptions
|
|
70
|
+
if (rule.exceptions && rule.exceptions.some(ex => file.includes(ex))) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (pattern.test(content)) {
|
|
75
|
+
// Check for architect approval
|
|
76
|
+
const hasApproval = content.includes(APPROVALS_TAG);
|
|
77
|
+
|
|
78
|
+
if (!hasApproval) {
|
|
79
|
+
violations.push({
|
|
80
|
+
file,
|
|
81
|
+
rule: rule.name || 'Unnamed rule',
|
|
82
|
+
severity: rule.severity || 'error'
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Check required imports (rules)
|
|
90
|
+
if (schema.rules && Array.isArray(schema.rules)) {
|
|
91
|
+
for (const rule of schema.rules) {
|
|
92
|
+
if (!rule.pattern || !rule.requiredImports) continue;
|
|
93
|
+
|
|
94
|
+
const filePattern = typeof rule.pattern === 'string' ? new RegExp(rule.pattern) : rule.pattern;
|
|
95
|
+
|
|
96
|
+
if (!filePattern.test(file)) continue;
|
|
97
|
+
|
|
98
|
+
// Check if file is in exceptions
|
|
99
|
+
if (rule.exceptions && rule.exceptions.some(ex => file.includes(ex))) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Check each required import
|
|
104
|
+
for (const requiredImport of rule.requiredImports) {
|
|
105
|
+
const importPattern = new RegExp(`import.*${requiredImport}`, 'i');
|
|
106
|
+
|
|
107
|
+
if (!importPattern.test(content)) {
|
|
108
|
+
// Check for architect approval
|
|
109
|
+
const hasApproval = content.includes(APPROVALS_TAG);
|
|
110
|
+
|
|
111
|
+
if (!hasApproval) {
|
|
112
|
+
violations.push({
|
|
113
|
+
file,
|
|
114
|
+
rule: `${rule.name || 'Unnamed rule'}: missing import '${requiredImport}'`,
|
|
115
|
+
severity: rule.severity || 'error'
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Report violations
|
|
125
|
+
if (violations.length > 0) {
|
|
126
|
+
console.error('\n❌ Architecture violations detected:\n');
|
|
127
|
+
|
|
128
|
+
for (const v of violations) {
|
|
129
|
+
const icon = v.severity === 'error' ? '🔴' : '⚠️';
|
|
130
|
+
console.error(`${icon} [${v.severity.toUpperCase()}] ${v.file}`);
|
|
131
|
+
console.error(` ${v.rule}`);
|
|
132
|
+
console.error(` Add ${APPROVALS_TAG} comment to override\n`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const errorCount = violations.filter(v => v.severity === 'error').length;
|
|
136
|
+
if (errorCount > 0) {
|
|
137
|
+
console.error(`\n❌ Commit blocked: ${errorCount} error(s) found`);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
38
142
|
console.log('✅ All checks passed');
|
|
39
143
|
}
|
|
40
144
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cerber-core",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "Your architecture roadmap becomes executable law. Guardian blocks commits that violate your plan. Pay architect once, Cerber enforces forever. Save $6k/month/dev.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -20,12 +20,14 @@ export const BACKEND_SCHEMA = {
|
|
|
20
20
|
forbiddenPatterns: [
|
|
21
21
|
// Uncomment and customize based on your tech stack:
|
|
22
22
|
// {
|
|
23
|
-
// pattern:
|
|
23
|
+
// pattern: "password\\s*=\\s*['\"][^'\"]+['\"]",
|
|
24
|
+
// flags: "i",
|
|
24
25
|
// name: "Hardcoded passwords",
|
|
25
26
|
// severity: "error"
|
|
26
27
|
// },
|
|
27
28
|
// {
|
|
28
|
-
// pattern:
|
|
29
|
+
// pattern: "api[_-]?key\\s*=\\s*['\"][^'\"]+['\"]",
|
|
30
|
+
// flags: "i",
|
|
29
31
|
// name: "Hardcoded API keys",
|
|
30
32
|
// severity: "error"
|
|
31
33
|
// }
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { execSync } from 'child_process';
|
|
6
6
|
import fs from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
7
8
|
|
|
8
9
|
const SCHEMA_FILE = '{{SCHEMA_FILE}}';
|
|
9
10
|
const APPROVALS_TAG = '{{APPROVALS_TAG}}';
|
|
@@ -13,10 +14,23 @@ async function main() {
|
|
|
13
14
|
|
|
14
15
|
if (!fs.existsSync(SCHEMA_FILE)) {
|
|
15
16
|
console.error(`❌ Schema file not found: ${SCHEMA_FILE}`);
|
|
16
|
-
console.error('
|
|
17
|
+
console.error('Create your schema file to enable validation.');
|
|
18
|
+
console.error(`Example: npx cerber init --print-schema-template > ${SCHEMA_FILE}`);
|
|
17
19
|
process.exit(1);
|
|
18
20
|
}
|
|
19
21
|
|
|
22
|
+
// Import schema
|
|
23
|
+
let schema;
|
|
24
|
+
try {
|
|
25
|
+
const schemaPath = join(process.cwd(), SCHEMA_FILE);
|
|
26
|
+
const schemaModule = await import(`file://${schemaPath}`);
|
|
27
|
+
schema = schemaModule.BACKEND_SCHEMA || schemaModule.default || schemaModule;
|
|
28
|
+
} catch (err) {
|
|
29
|
+
console.error(`❌ Failed to load schema from ${SCHEMA_FILE}:`, err.message);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Get staged files
|
|
20
34
|
let stagedFiles;
|
|
21
35
|
try {
|
|
22
36
|
stagedFiles = execSync('git diff --cached --name-only', { encoding: 'utf-8' })
|
|
@@ -34,7 +48,97 @@ async function main() {
|
|
|
34
48
|
}
|
|
35
49
|
|
|
36
50
|
console.log(`Checking ${stagedFiles.length} file(s)...`);
|
|
37
|
-
|
|
51
|
+
|
|
52
|
+
const violations = [];
|
|
53
|
+
|
|
54
|
+
// Validate each staged file
|
|
55
|
+
for (const file of stagedFiles) {
|
|
56
|
+
if (!fs.existsSync(file)) continue;
|
|
57
|
+
|
|
58
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
59
|
+
|
|
60
|
+
// Check forbidden patterns
|
|
61
|
+
if (schema.forbiddenPatterns && Array.isArray(schema.forbiddenPatterns)) {
|
|
62
|
+
for (const rule of schema.forbiddenPatterns) {
|
|
63
|
+
if (!rule.pattern) continue;
|
|
64
|
+
|
|
65
|
+
const pattern = typeof rule.pattern === 'string'
|
|
66
|
+
? new RegExp(rule.pattern, rule.flags || 'i')
|
|
67
|
+
: rule.pattern;
|
|
68
|
+
|
|
69
|
+
// Check if file is in exceptions
|
|
70
|
+
if (rule.exceptions && rule.exceptions.some(ex => file.includes(ex))) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (pattern.test(content)) {
|
|
75
|
+
// Check for architect approval
|
|
76
|
+
const hasApproval = content.includes(APPROVALS_TAG);
|
|
77
|
+
|
|
78
|
+
if (!hasApproval) {
|
|
79
|
+
violations.push({
|
|
80
|
+
file,
|
|
81
|
+
rule: rule.name || 'Unnamed rule',
|
|
82
|
+
severity: rule.severity || 'error'
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Check required imports (rules)
|
|
90
|
+
if (schema.rules && Array.isArray(schema.rules)) {
|
|
91
|
+
for (const rule of schema.rules) {
|
|
92
|
+
if (!rule.pattern || !rule.requiredImports) continue;
|
|
93
|
+
|
|
94
|
+
const filePattern = typeof rule.pattern === 'string' ? new RegExp(rule.pattern) : rule.pattern;
|
|
95
|
+
|
|
96
|
+
if (!filePattern.test(file)) continue;
|
|
97
|
+
|
|
98
|
+
// Check if file is in exceptions
|
|
99
|
+
if (rule.exceptions && rule.exceptions.some(ex => file.includes(ex))) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Check each required import
|
|
104
|
+
for (const requiredImport of rule.requiredImports) {
|
|
105
|
+
const importPattern = new RegExp(`import.*${requiredImport}`, 'i');
|
|
106
|
+
|
|
107
|
+
if (!importPattern.test(content)) {
|
|
108
|
+
// Check for architect approval
|
|
109
|
+
const hasApproval = content.includes(APPROVALS_TAG);
|
|
110
|
+
|
|
111
|
+
if (!hasApproval) {
|
|
112
|
+
violations.push({
|
|
113
|
+
file,
|
|
114
|
+
rule: `${rule.name || 'Unnamed rule'}: missing import '${requiredImport}'`,
|
|
115
|
+
severity: rule.severity || 'error'
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Report violations
|
|
125
|
+
if (violations.length > 0) {
|
|
126
|
+
console.error('\n❌ Architecture violations detected:\n');
|
|
127
|
+
|
|
128
|
+
for (const v of violations) {
|
|
129
|
+
const icon = v.severity === 'error' ? '🔴' : '⚠️';
|
|
130
|
+
console.error(`${icon} [${v.severity.toUpperCase()}] ${v.file}`);
|
|
131
|
+
console.error(` ${v.rule}`);
|
|
132
|
+
console.error(` Add ${APPROVALS_TAG} comment to override\n`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const errorCount = violations.filter(v => v.severity === 'error').length;
|
|
136
|
+
if (errorCount > 0) {
|
|
137
|
+
console.error(`\n❌ Commit blocked: ${errorCount} error(s) found`);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
38
142
|
console.log('✅ All checks passed');
|
|
39
143
|
}
|
|
40
144
|
|
|
@@ -20,12 +20,14 @@ export const BACKEND_SCHEMA = {
|
|
|
20
20
|
forbiddenPatterns: [
|
|
21
21
|
// Uncomment and customize based on your tech stack:
|
|
22
22
|
// {
|
|
23
|
-
// pattern:
|
|
23
|
+
// pattern: "password\\s*=\\s*['\"][^'\"]+['\"]",
|
|
24
|
+
// flags: "i",
|
|
24
25
|
// name: "Hardcoded passwords",
|
|
25
26
|
// severity: "error"
|
|
26
27
|
// },
|
|
27
28
|
// {
|
|
28
|
-
// pattern:
|
|
29
|
+
// pattern: "api[_-]?key\\s*=\\s*['\"][^'\"]+['\"]",
|
|
30
|
+
// flags: "i",
|
|
29
31
|
// name: "Hardcoded API keys",
|
|
30
32
|
// severity: "error"
|
|
31
33
|
// }
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { execSync } from 'child_process';
|
|
6
6
|
import fs from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
7
8
|
|
|
8
9
|
const SCHEMA_FILE = '{{SCHEMA_FILE}}';
|
|
9
10
|
const APPROVALS_TAG = '{{APPROVALS_TAG}}';
|
|
@@ -13,10 +14,23 @@ async function main() {
|
|
|
13
14
|
|
|
14
15
|
if (!fs.existsSync(SCHEMA_FILE)) {
|
|
15
16
|
console.error(`❌ Schema file not found: ${SCHEMA_FILE}`);
|
|
16
|
-
console.error('
|
|
17
|
+
console.error('Create your schema file to enable validation.');
|
|
18
|
+
console.error(`Example: npx cerber init --print-schema-template > ${SCHEMA_FILE}`);
|
|
17
19
|
process.exit(1);
|
|
18
20
|
}
|
|
19
21
|
|
|
22
|
+
// Import schema
|
|
23
|
+
let schema;
|
|
24
|
+
try {
|
|
25
|
+
const schemaPath = join(process.cwd(), SCHEMA_FILE);
|
|
26
|
+
const schemaModule = await import(`file://${schemaPath}`);
|
|
27
|
+
schema = schemaModule.BACKEND_SCHEMA || schemaModule.default || schemaModule;
|
|
28
|
+
} catch (err) {
|
|
29
|
+
console.error(`❌ Failed to load schema from ${SCHEMA_FILE}:`, err.message);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Get staged files
|
|
20
34
|
let stagedFiles;
|
|
21
35
|
try {
|
|
22
36
|
stagedFiles = execSync('git diff --cached --name-only', { encoding: 'utf-8' })
|
|
@@ -34,7 +48,97 @@ async function main() {
|
|
|
34
48
|
}
|
|
35
49
|
|
|
36
50
|
console.log(`Checking ${stagedFiles.length} file(s)...`);
|
|
37
|
-
|
|
51
|
+
|
|
52
|
+
const violations = [];
|
|
53
|
+
|
|
54
|
+
// Validate each staged file
|
|
55
|
+
for (const file of stagedFiles) {
|
|
56
|
+
if (!fs.existsSync(file)) continue;
|
|
57
|
+
|
|
58
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
59
|
+
|
|
60
|
+
// Check forbidden patterns
|
|
61
|
+
if (schema.forbiddenPatterns && Array.isArray(schema.forbiddenPatterns)) {
|
|
62
|
+
for (const rule of schema.forbiddenPatterns) {
|
|
63
|
+
if (!rule.pattern) continue;
|
|
64
|
+
|
|
65
|
+
const pattern = typeof rule.pattern === 'string'
|
|
66
|
+
? new RegExp(rule.pattern, rule.flags || 'i')
|
|
67
|
+
: rule.pattern;
|
|
68
|
+
|
|
69
|
+
// Check if file is in exceptions
|
|
70
|
+
if (rule.exceptions && rule.exceptions.some(ex => file.includes(ex))) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (pattern.test(content)) {
|
|
75
|
+
// Check for architect approval
|
|
76
|
+
const hasApproval = content.includes(APPROVALS_TAG);
|
|
77
|
+
|
|
78
|
+
if (!hasApproval) {
|
|
79
|
+
violations.push({
|
|
80
|
+
file,
|
|
81
|
+
rule: rule.name || 'Unnamed rule',
|
|
82
|
+
severity: rule.severity || 'error'
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Check required imports (rules)
|
|
90
|
+
if (schema.rules && Array.isArray(schema.rules)) {
|
|
91
|
+
for (const rule of schema.rules) {
|
|
92
|
+
if (!rule.pattern || !rule.requiredImports) continue;
|
|
93
|
+
|
|
94
|
+
const filePattern = typeof rule.pattern === 'string' ? new RegExp(rule.pattern) : rule.pattern;
|
|
95
|
+
|
|
96
|
+
if (!filePattern.test(file)) continue;
|
|
97
|
+
|
|
98
|
+
// Check if file is in exceptions
|
|
99
|
+
if (rule.exceptions && rule.exceptions.some(ex => file.includes(ex))) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Check each required import
|
|
104
|
+
for (const requiredImport of rule.requiredImports) {
|
|
105
|
+
const importPattern = new RegExp(`import.*${requiredImport}`, 'i');
|
|
106
|
+
|
|
107
|
+
if (!importPattern.test(content)) {
|
|
108
|
+
// Check for architect approval
|
|
109
|
+
const hasApproval = content.includes(APPROVALS_TAG);
|
|
110
|
+
|
|
111
|
+
if (!hasApproval) {
|
|
112
|
+
violations.push({
|
|
113
|
+
file,
|
|
114
|
+
rule: `${rule.name || 'Unnamed rule'}: missing import '${requiredImport}'`,
|
|
115
|
+
severity: rule.severity || 'error'
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Report violations
|
|
125
|
+
if (violations.length > 0) {
|
|
126
|
+
console.error('\n❌ Architecture violations detected:\n');
|
|
127
|
+
|
|
128
|
+
for (const v of violations) {
|
|
129
|
+
const icon = v.severity === 'error' ? '🔴' : '⚠️';
|
|
130
|
+
console.error(`${icon} [${v.severity.toUpperCase()}] ${v.file}`);
|
|
131
|
+
console.error(` ${v.rule}`);
|
|
132
|
+
console.error(` Add ${APPROVALS_TAG} comment to override\n`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const errorCount = violations.filter(v => v.severity === 'error').length;
|
|
136
|
+
if (errorCount > 0) {
|
|
137
|
+
console.error(`\n❌ Commit blocked: ${errorCount} error(s) found`);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
38
142
|
console.log('✅ All checks passed');
|
|
39
143
|
}
|
|
40
144
|
|