cerber-core 1.1.2 ā 1.1.4
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/CHANGELOG.md +33 -0
- package/dev/templates/BACKEND_SCHEMA.ts.tpl +50 -48
- package/dev/templates/cerber-guardian.mjs.tpl +106 -2
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +9 -4
- package/dist/cli/init.js.map +1 -1
- package/package.json +1 -1
- package/solo/templates/BACKEND_SCHEMA.ts.tpl +50 -48
- package/solo/templates/cerber-guardian.mjs.tpl +106 -2
- package/team/templates/BACKEND_SCHEMA.ts.tpl +50 -48
- package/team/templates/cerber-guardian.mjs.tpl +106 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,39 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.1.4] - 2026-01-04
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **CLI Template Generator**: `--mode` flag now correctly sets mode in generated CERBER.md
|
|
13
|
+
- `npx cerber init --mode=team` now creates template with `mode: team` (not hardcoded `dev`)
|
|
14
|
+
- Applies to solo/dev/team modes
|
|
15
|
+
- Fixed E2E testing workflow
|
|
16
|
+
|
|
17
|
+
## [1.1.3] - 2026-01-04
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
#### š CRITICAL: Guardian Validation Logic
|
|
22
|
+
- **v1.1.2 shipped with non-functional Guardian** (MVP placeholder that always passed)
|
|
23
|
+
- Implemented full forbiddenPatterns validation logic
|
|
24
|
+
- Implemented requiredImports checking with architect approval override
|
|
25
|
+
- Fixed RegExp serialization issue (using string patterns with flags field)
|
|
26
|
+
- Guardian now **actually blocks commits** that violate architecture rules
|
|
27
|
+
- Removed debug output from production template
|
|
28
|
+
|
|
29
|
+
#### ā
E2E Testing Results
|
|
30
|
+
- **TEST 1 PASSED**: Guardian successfully blocks commits with violations
|
|
31
|
+
- Tested with: `const password = "admin123";`
|
|
32
|
+
- Result: ā Commit blocked with clear error message
|
|
33
|
+
- Husky pre-commit hook working correctly
|
|
34
|
+
- Fresh install from npm verified
|
|
35
|
+
|
|
36
|
+
### Breaking Fix
|
|
37
|
+
This is technically a BREAKING FIX because v1.1.2 did not enforce any validations. Projects that relied on the previous behavior (no enforcement) will now have commits blocked if they violate patterns defined in BACKEND_SCHEMA.ts.
|
|
38
|
+
|
|
39
|
+
**Migration**: If you have v1.1.2, upgrade to v1.1.3 immediately. Review and customize your `BACKEND_SCHEMA.ts` patterns before enabling in production.
|
|
40
|
+
|
|
8
41
|
## [1.1.0] - 2026-01-03
|
|
9
42
|
|
|
10
43
|
### Added
|
|
@@ -1,48 +1,50 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ā ļø CERBER TEMPLATE (NOT SOURCE OF TRUTH)
|
|
3
|
-
*
|
|
4
|
-
* This file is a starting point. Edit it to match YOUR architecture.
|
|
5
|
-
* The source of truth is CERBER.md.
|
|
6
|
-
*
|
|
7
|
-
* Generated by: npx cerber init
|
|
8
|
-
* Project: {{PROJECT_NAME}}
|
|
9
|
-
*
|
|
10
|
-
* See: https://github.com/Agaslez/cerber-core#guardian-configuration
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
export const BACKEND_SCHEMA = {
|
|
14
|
-
version: "1.0.0",
|
|
15
|
-
|
|
16
|
-
// Your architecture rules (customize)
|
|
17
|
-
rules: [],
|
|
18
|
-
|
|
19
|
-
// Patterns that should never appear in your code
|
|
20
|
-
forbiddenPatterns: [
|
|
21
|
-
// Uncomment and customize based on your tech stack:
|
|
22
|
-
// {
|
|
23
|
-
// pattern:
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
|
|
1
|
+
/**
|
|
2
|
+
* ā ļø CERBER TEMPLATE (NOT SOURCE OF TRUTH)
|
|
3
|
+
*
|
|
4
|
+
* This file is a starting point. Edit it to match YOUR architecture.
|
|
5
|
+
* The source of truth is CERBER.md.
|
|
6
|
+
*
|
|
7
|
+
* Generated by: npx cerber init
|
|
8
|
+
* Project: {{PROJECT_NAME}}
|
|
9
|
+
*
|
|
10
|
+
* See: https://github.com/Agaslez/cerber-core#guardian-configuration
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export const BACKEND_SCHEMA = {
|
|
14
|
+
version: "1.0.0",
|
|
15
|
+
|
|
16
|
+
// Your architecture rules (customize)
|
|
17
|
+
rules: [],
|
|
18
|
+
|
|
19
|
+
// Patterns that should never appear in your code
|
|
20
|
+
forbiddenPatterns: [
|
|
21
|
+
// Uncomment and customize based on your tech stack:
|
|
22
|
+
// {
|
|
23
|
+
// pattern: "password\\s*=\\s*['\"][^'\"]+['\"]",
|
|
24
|
+
// flags: "i",
|
|
25
|
+
// name: "Hardcoded passwords",
|
|
26
|
+
// severity: "error"
|
|
27
|
+
// },
|
|
28
|
+
// {
|
|
29
|
+
// pattern: "api[_-]?key\\s*=\\s*['\"][^'\"]+['\"]",
|
|
30
|
+
// flags: "i",
|
|
31
|
+
// name: "Hardcoded API keys",
|
|
32
|
+
// severity: "error"
|
|
33
|
+
// }
|
|
34
|
+
]
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default BACKEND_SCHEMA;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* š” Next Steps:
|
|
41
|
+
*
|
|
42
|
+
* 1. Define your project structure in CERBER.md (single source of truth)
|
|
43
|
+
* 2. Add forbiddenPatterns for your tech stack
|
|
44
|
+
* 3. Add requiredImports rules for your architecture layers
|
|
45
|
+
* 4. Test with: npx cerber-guardian
|
|
46
|
+
*
|
|
47
|
+
* Full examples:
|
|
48
|
+
* - node_modules/cerber-core/examples/backend-schema.ts
|
|
49
|
+
* - node_modules/cerber-core/examples/frontend-schema.ts
|
|
50
|
+
*/
|
|
@@ -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/dist/cli/init.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,OAAO,EAAiC,WAAW,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,OAAO,EAAiC,WAAW,EAAE,MAAM,YAAY,CAAC;AAuFxE,wBAAsB,WAAW,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0G1E"}
|
package/dist/cli/init.js
CHANGED
|
@@ -11,14 +11,15 @@ import fs from 'fs/promises';
|
|
|
11
11
|
import path from 'path';
|
|
12
12
|
import { getDefaultContract, parseCerberContract } from './contract-parser.js';
|
|
13
13
|
import { TemplateGenerator } from './template-generator.js';
|
|
14
|
-
|
|
14
|
+
function getCerberMdTemplate(mode = 'dev') {
|
|
15
|
+
return `# CERBER.md - Architecture Roadmap
|
|
15
16
|
|
|
16
17
|
> **This is your single source of truth. AI agents and developers enforce this contract.**
|
|
17
18
|
|
|
18
19
|
## CERBER_CONTRACT
|
|
19
20
|
\`\`\`yaml
|
|
20
21
|
version: 1
|
|
21
|
-
mode:
|
|
22
|
+
mode: ${mode} # solo | dev | team
|
|
22
23
|
|
|
23
24
|
guardian:
|
|
24
25
|
enabled: true
|
|
@@ -93,11 +94,13 @@ See \`${getDefaultContract().guardian.schemaFile}\` for complete architecture ru
|
|
|
93
94
|
|
|
94
95
|
*This file is protected by CODEOWNERS. Changes require architect approval.*
|
|
95
96
|
`;
|
|
97
|
+
}
|
|
96
98
|
export async function initCommand(options = {}) {
|
|
97
99
|
const projectRoot = process.cwd();
|
|
98
100
|
// Handle --print-template flag
|
|
99
101
|
if (options.printTemplate) {
|
|
100
|
-
|
|
102
|
+
const templateMode = options.mode || 'dev';
|
|
103
|
+
console.log(getCerberMdTemplate(templateMode));
|
|
101
104
|
return;
|
|
102
105
|
}
|
|
103
106
|
console.log(chalk.bold.cyan('š”ļø Cerber Core - Project Initialization'));
|
|
@@ -116,8 +119,10 @@ export async function initCommand(options = {}) {
|
|
|
116
119
|
console.log(chalk.yellow('š CERBER.md not found'));
|
|
117
120
|
console.log('Creating template...');
|
|
118
121
|
console.log('');
|
|
122
|
+
const templateMode = options.mode || 'dev';
|
|
123
|
+
const template = getCerberMdTemplate(templateMode);
|
|
119
124
|
if (!options.dryRun) {
|
|
120
|
-
await fs.writeFile(cerberPath,
|
|
125
|
+
await fs.writeFile(cerberPath, template, 'utf-8');
|
|
121
126
|
console.log(chalk.green('ā
Created CERBER.md'));
|
|
122
127
|
}
|
|
123
128
|
else {
|
package/dist/cli/init.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,MAAM,aAAa,CAAC;AAC7B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC/E,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAG5D,
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cerber-core",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
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",
|
|
@@ -1,48 +1,50 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ā ļø CERBER TEMPLATE (NOT SOURCE OF TRUTH)
|
|
3
|
-
*
|
|
4
|
-
* This file is a starting point. Edit it to match YOUR architecture.
|
|
5
|
-
* The source of truth is CERBER.md.
|
|
6
|
-
*
|
|
7
|
-
* Generated by: npx cerber init
|
|
8
|
-
* Project: {{PROJECT_NAME}}
|
|
9
|
-
*
|
|
10
|
-
* See: https://github.com/Agaslez/cerber-core#guardian-configuration
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
export const BACKEND_SCHEMA = {
|
|
14
|
-
version: "1.0.0",
|
|
15
|
-
|
|
16
|
-
// Your architecture rules (customize)
|
|
17
|
-
rules: [],
|
|
18
|
-
|
|
19
|
-
// Patterns that should never appear in your code
|
|
20
|
-
forbiddenPatterns: [
|
|
21
|
-
// Uncomment and customize based on your tech stack:
|
|
22
|
-
// {
|
|
23
|
-
// pattern:
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
|
|
1
|
+
/**
|
|
2
|
+
* ā ļø CERBER TEMPLATE (NOT SOURCE OF TRUTH)
|
|
3
|
+
*
|
|
4
|
+
* This file is a starting point. Edit it to match YOUR architecture.
|
|
5
|
+
* The source of truth is CERBER.md.
|
|
6
|
+
*
|
|
7
|
+
* Generated by: npx cerber init
|
|
8
|
+
* Project: {{PROJECT_NAME}}
|
|
9
|
+
*
|
|
10
|
+
* See: https://github.com/Agaslez/cerber-core#guardian-configuration
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export const BACKEND_SCHEMA = {
|
|
14
|
+
version: "1.0.0",
|
|
15
|
+
|
|
16
|
+
// Your architecture rules (customize)
|
|
17
|
+
rules: [],
|
|
18
|
+
|
|
19
|
+
// Patterns that should never appear in your code
|
|
20
|
+
forbiddenPatterns: [
|
|
21
|
+
// Uncomment and customize based on your tech stack:
|
|
22
|
+
// {
|
|
23
|
+
// pattern: "password\\s*=\\s*['\"][^'\"]+['\"]",
|
|
24
|
+
// flags: "i",
|
|
25
|
+
// name: "Hardcoded passwords",
|
|
26
|
+
// severity: "error"
|
|
27
|
+
// },
|
|
28
|
+
// {
|
|
29
|
+
// pattern: "api[_-]?key\\s*=\\s*['\"][^'\"]+['\"]",
|
|
30
|
+
// flags: "i",
|
|
31
|
+
// name: "Hardcoded API keys",
|
|
32
|
+
// severity: "error"
|
|
33
|
+
// }
|
|
34
|
+
]
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default BACKEND_SCHEMA;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* š” Next Steps:
|
|
41
|
+
*
|
|
42
|
+
* 1. Define your project structure in CERBER.md (single source of truth)
|
|
43
|
+
* 2. Add forbiddenPatterns for your tech stack
|
|
44
|
+
* 3. Add requiredImports rules for your architecture layers
|
|
45
|
+
* 4. Test with: npx cerber-guardian
|
|
46
|
+
*
|
|
47
|
+
* Full examples:
|
|
48
|
+
* - node_modules/cerber-core/examples/backend-schema.ts
|
|
49
|
+
* - node_modules/cerber-core/examples/frontend-schema.ts
|
|
50
|
+
*/
|
|
@@ -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
|
|
|
@@ -1,48 +1,50 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ā ļø CERBER TEMPLATE (NOT SOURCE OF TRUTH)
|
|
3
|
-
*
|
|
4
|
-
* This file is a starting point. Edit it to match YOUR architecture.
|
|
5
|
-
* The source of truth is CERBER.md.
|
|
6
|
-
*
|
|
7
|
-
* Generated by: npx cerber init
|
|
8
|
-
* Project: {{PROJECT_NAME}}
|
|
9
|
-
*
|
|
10
|
-
* See: https://github.com/Agaslez/cerber-core#guardian-configuration
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
export const BACKEND_SCHEMA = {
|
|
14
|
-
version: "1.0.0",
|
|
15
|
-
|
|
16
|
-
// Your architecture rules (customize)
|
|
17
|
-
rules: [],
|
|
18
|
-
|
|
19
|
-
// Patterns that should never appear in your code
|
|
20
|
-
forbiddenPatterns: [
|
|
21
|
-
// Uncomment and customize based on your tech stack:
|
|
22
|
-
// {
|
|
23
|
-
// pattern:
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
|
|
1
|
+
/**
|
|
2
|
+
* ā ļø CERBER TEMPLATE (NOT SOURCE OF TRUTH)
|
|
3
|
+
*
|
|
4
|
+
* This file is a starting point. Edit it to match YOUR architecture.
|
|
5
|
+
* The source of truth is CERBER.md.
|
|
6
|
+
*
|
|
7
|
+
* Generated by: npx cerber init
|
|
8
|
+
* Project: {{PROJECT_NAME}}
|
|
9
|
+
*
|
|
10
|
+
* See: https://github.com/Agaslez/cerber-core#guardian-configuration
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export const BACKEND_SCHEMA = {
|
|
14
|
+
version: "1.0.0",
|
|
15
|
+
|
|
16
|
+
// Your architecture rules (customize)
|
|
17
|
+
rules: [],
|
|
18
|
+
|
|
19
|
+
// Patterns that should never appear in your code
|
|
20
|
+
forbiddenPatterns: [
|
|
21
|
+
// Uncomment and customize based on your tech stack:
|
|
22
|
+
// {
|
|
23
|
+
// pattern: "password\\s*=\\s*['\"][^'\"]+['\"]",
|
|
24
|
+
// flags: "i",
|
|
25
|
+
// name: "Hardcoded passwords",
|
|
26
|
+
// severity: "error"
|
|
27
|
+
// },
|
|
28
|
+
// {
|
|
29
|
+
// pattern: "api[_-]?key\\s*=\\s*['\"][^'\"]+['\"]",
|
|
30
|
+
// flags: "i",
|
|
31
|
+
// name: "Hardcoded API keys",
|
|
32
|
+
// severity: "error"
|
|
33
|
+
// }
|
|
34
|
+
]
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default BACKEND_SCHEMA;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* š” Next Steps:
|
|
41
|
+
*
|
|
42
|
+
* 1. Define your project structure in CERBER.md (single source of truth)
|
|
43
|
+
* 2. Add forbiddenPatterns for your tech stack
|
|
44
|
+
* 3. Add requiredImports rules for your architecture layers
|
|
45
|
+
* 4. Test with: npx cerber-guardian
|
|
46
|
+
*
|
|
47
|
+
* Full examples:
|
|
48
|
+
* - node_modules/cerber-core/examples/backend-schema.ts
|
|
49
|
+
* - node_modules/cerber-core/examples/frontend-schema.ts
|
|
50
|
+
*/
|
|
@@ -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
|
|