cerber-core 1.0.4 → 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/CHANGELOG.md +158 -68
- package/README.md +198 -5
- package/USAGE_GUIDE.md +254 -0
- package/bin/cerber +120 -104
- package/dev/templates/BACKEND_SCHEMA.ts.tpl +48 -0
- package/dev/templates/cerber-guardian.mjs.tpl +44 -0
- package/dev/templates/cerber.yml.tpl +53 -0
- package/dev/templates/health-checks.ts.tpl +11 -0
- package/dev/templates/health-route.ts.tpl +50 -0
- package/dev/templates/pre-commit.tpl +4 -0
- package/dist/cli/contract-parser.d.ts +13 -0
- package/dist/cli/contract-parser.d.ts.map +1 -0
- package/dist/cli/contract-parser.js +265 -0
- package/dist/cli/contract-parser.js.map +1 -0
- package/dist/cli/init.d.ts +11 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +292 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/template-generator.d.ts +29 -0
- package/dist/cli/template-generator.d.ts.map +1 -0
- package/dist/cli/template-generator.js +252 -0
- package/dist/cli/template-generator.js.map +1 -0
- package/dist/cli/types.d.ts +79 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js +8 -0
- package/dist/cli/types.js.map +1 -0
- package/examples/backend-schema.ts +9 -2
- package/examples/frontend-schema.ts +9 -2
- package/package.json +106 -104
- package/solo/templates/BACKEND_SCHEMA.ts.tpl +48 -0
- package/solo/templates/cerber-guardian.mjs.tpl +44 -0
- package/solo/templates/cerber.yml.tpl +53 -0
- package/solo/templates/health-checks.ts.tpl +29 -0
- package/solo/templates/health-route.ts.tpl +50 -0
- package/solo/templates/pre-commit.tpl +4 -0
- package/team/templates/BACKEND_SCHEMA.ts.tpl +48 -0
- package/team/templates/CODEOWNERS.tpl +6 -0
- package/team/templates/cerber-guardian.mjs.tpl +44 -0
- package/team/templates/cerber.yml.tpl +53 -0
- package/team/templates/health-checks.ts.tpl +10 -0
- package/team/templates/health-route.ts.tpl +50 -0
- package/team/templates/pre-commit.tpl +4 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Generated by Cerber init - CUSTOMIZE THIS FILE
|
|
2
|
+
|
|
3
|
+
import { checks } from './health-checks.js';
|
|
4
|
+
|
|
5
|
+
export async function healthHandler(req: any, res: any) {
|
|
6
|
+
const startTime = Date.now();
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
const results = await Promise.all(
|
|
10
|
+
Object.entries(checks).map(async ([name, check]) => ({
|
|
11
|
+
name,
|
|
12
|
+
issues: await check()
|
|
13
|
+
}))
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const allIssues = results.flatMap(r => r.issues);
|
|
17
|
+
const critical = allIssues.filter(i => i.severity === 'critical').length;
|
|
18
|
+
const errors = allIssues.filter(i => i.severity === 'error').length;
|
|
19
|
+
const warnings = allIssues.filter(i => i.severity === 'warning').length;
|
|
20
|
+
|
|
21
|
+
const status = critical > 0 ? 'unhealthy' :
|
|
22
|
+
errors > 0 ? 'degraded' : 'healthy';
|
|
23
|
+
|
|
24
|
+
const statusCode = status === 'healthy' ? 200 : 503;
|
|
25
|
+
|
|
26
|
+
res.status(statusCode).json({
|
|
27
|
+
status,
|
|
28
|
+
timestamp: new Date().toISOString(),
|
|
29
|
+
durationMs: Date.now() - startTime,
|
|
30
|
+
summary: {
|
|
31
|
+
totalChecks: results.length,
|
|
32
|
+
failedChecks: results.filter(r => r.issues.length > 0).length,
|
|
33
|
+
criticalIssues: critical,
|
|
34
|
+
errorIssues: errors,
|
|
35
|
+
warningIssues: warnings
|
|
36
|
+
},
|
|
37
|
+
components: allIssues
|
|
38
|
+
});
|
|
39
|
+
} catch (err: any) {
|
|
40
|
+
res.status(503).json({
|
|
41
|
+
status: 'error',
|
|
42
|
+
message: 'Health check failed',
|
|
43
|
+
error: err.message
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Usage in your server:
|
|
49
|
+
// import { healthHandler } from './cerber/health-route.js';
|
|
50
|
+
// app.get('{{HEALTH_ENDPOINT}}', healthHandler);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CERBER_CONTRACT parser
|
|
3
|
+
*
|
|
4
|
+
* Extracts and validates YAML contract from CERBER.md
|
|
5
|
+
*
|
|
6
|
+
* @author Stefan Pitek
|
|
7
|
+
* @license MIT
|
|
8
|
+
*/
|
|
9
|
+
import { CerberContract, ContractParseResult } from './types.js';
|
|
10
|
+
export declare function parseCerberContract(projectRoot: string): Promise<ContractParseResult>;
|
|
11
|
+
export declare function extractContract(content: string): ContractParseResult;
|
|
12
|
+
export declare function getDefaultContract(mode?: 'solo' | 'dev' | 'team'): CerberContract;
|
|
13
|
+
//# sourceMappingURL=contract-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-parser.d.ts","sourceRoot":"","sources":["../../src/cli/contract-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAMjE,wBAAsB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAU3F;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,mBAAmB,CA8EpE;AAyID,wBAAgB,kBAAkB,CAAC,IAAI,GAAE,MAAM,GAAG,KAAK,GAAG,MAAc,GAAG,cAAc,CAqCxF"}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CERBER_CONTRACT parser
|
|
3
|
+
*
|
|
4
|
+
* Extracts and validates YAML contract from CERBER.md
|
|
5
|
+
*
|
|
6
|
+
* @author Stefan Pitek
|
|
7
|
+
* @license MIT
|
|
8
|
+
*/
|
|
9
|
+
import fs from 'fs/promises';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
const YAML_START_MARKER = '## CERBER_CONTRACT';
|
|
12
|
+
const YAML_CODE_BLOCK_START = '```yaml';
|
|
13
|
+
const YAML_CODE_BLOCK_END = '```';
|
|
14
|
+
export async function parseCerberContract(projectRoot) {
|
|
15
|
+
const cerberPath = path.join(projectRoot, 'CERBER.md');
|
|
16
|
+
try {
|
|
17
|
+
const content = await fs.readFile(cerberPath, 'utf-8');
|
|
18
|
+
return extractContract(content);
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
// CERBER.md doesn't exist
|
|
22
|
+
return { success: false, error: { message: 'CERBER.md not found' } };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function extractContract(content) {
|
|
26
|
+
const lines = content.split('\n');
|
|
27
|
+
// Find CERBER_CONTRACT section
|
|
28
|
+
const contractStartIndex = lines.findIndex(line => line.trim() === YAML_START_MARKER);
|
|
29
|
+
if (contractStartIndex === -1) {
|
|
30
|
+
return {
|
|
31
|
+
success: false,
|
|
32
|
+
error: {
|
|
33
|
+
message: `Missing "${YAML_START_MARKER}" section header`,
|
|
34
|
+
context: 'Expected format:\n\n## CERBER_CONTRACT\n\`\`\`yaml\n...\n\`\`\`'
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
// Find yaml code block
|
|
39
|
+
let yamlStartIndex = -1;
|
|
40
|
+
let yamlEndIndex = -1;
|
|
41
|
+
for (let i = contractStartIndex; i < lines.length; i++) {
|
|
42
|
+
if (lines[i].trim().startsWith(YAML_CODE_BLOCK_START)) {
|
|
43
|
+
yamlStartIndex = i + 1;
|
|
44
|
+
}
|
|
45
|
+
else if (yamlStartIndex !== -1 && lines[i].trim() === YAML_CODE_BLOCK_END) {
|
|
46
|
+
yamlEndIndex = i;
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (yamlStartIndex === -1) {
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
error: {
|
|
54
|
+
message: 'Missing YAML code block after CERBER_CONTRACT header',
|
|
55
|
+
line: contractStartIndex + 1,
|
|
56
|
+
context: `Expected \`\`\`yaml after line ${contractStartIndex + 1}`
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (yamlEndIndex === -1) {
|
|
61
|
+
return {
|
|
62
|
+
success: false,
|
|
63
|
+
error: {
|
|
64
|
+
message: 'Unclosed YAML code block',
|
|
65
|
+
line: yamlStartIndex,
|
|
66
|
+
context: 'Missing closing \`\`\` for YAML block'
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const yamlContent = lines.slice(yamlStartIndex, yamlEndIndex).join('\n');
|
|
71
|
+
// Simple YAML parser (for our specific structure)
|
|
72
|
+
try {
|
|
73
|
+
const contract = parseSimpleYaml(yamlContent);
|
|
74
|
+
// Validate required fields
|
|
75
|
+
const validation = validateContract(contract);
|
|
76
|
+
if (!validation.valid) {
|
|
77
|
+
return {
|
|
78
|
+
success: false,
|
|
79
|
+
error: {
|
|
80
|
+
message: 'Invalid contract structure',
|
|
81
|
+
context: validation.errors.join('\n')
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return { success: true, contract };
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
return {
|
|
89
|
+
success: false,
|
|
90
|
+
error: {
|
|
91
|
+
message: 'Failed to parse YAML contract',
|
|
92
|
+
context: err.message || 'Invalid YAML structure'
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function parseSimpleYaml(yamlContent) {
|
|
98
|
+
const lines = yamlContent.split('\n').filter(line => line.trim() && !line.trim().startsWith('#'));
|
|
99
|
+
const contract = {
|
|
100
|
+
version: 1,
|
|
101
|
+
mode: 'dev',
|
|
102
|
+
guardian: {},
|
|
103
|
+
health: {},
|
|
104
|
+
ci: {},
|
|
105
|
+
schema: {},
|
|
106
|
+
team: {}
|
|
107
|
+
};
|
|
108
|
+
let currentSection = null;
|
|
109
|
+
let currentSubsection = null;
|
|
110
|
+
for (const line of lines) {
|
|
111
|
+
const trimmed = line.trim();
|
|
112
|
+
const indent = line.length - line.trimStart().length;
|
|
113
|
+
if (indent === 0 && trimmed.endsWith(':')) {
|
|
114
|
+
// Top-level key
|
|
115
|
+
const key = trimmed.slice(0, -1);
|
|
116
|
+
currentSection = key;
|
|
117
|
+
currentSubsection = null;
|
|
118
|
+
}
|
|
119
|
+
else if (indent === 2 && trimmed.endsWith(':')) {
|
|
120
|
+
// Second-level key
|
|
121
|
+
const key = trimmed.slice(0, -1);
|
|
122
|
+
currentSubsection = key;
|
|
123
|
+
if (currentSection && !contract[currentSection][key]) {
|
|
124
|
+
contract[currentSection][key] = {};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else if (trimmed.includes(':')) {
|
|
128
|
+
// Key-value pair
|
|
129
|
+
const [key, ...valueParts] = trimmed.split(':');
|
|
130
|
+
let value = valueParts.join(':').trim();
|
|
131
|
+
// Strip inline comments (# after value)
|
|
132
|
+
const commentIndex = value.indexOf('#');
|
|
133
|
+
if (commentIndex !== -1) {
|
|
134
|
+
value = value.substring(0, commentIndex).trim();
|
|
135
|
+
}
|
|
136
|
+
// Parse value type
|
|
137
|
+
if (value === 'true')
|
|
138
|
+
value = true;
|
|
139
|
+
else if (value === 'false')
|
|
140
|
+
value = false;
|
|
141
|
+
else if (!isNaN(Number(value)))
|
|
142
|
+
value = Number(value);
|
|
143
|
+
else if (value.startsWith('[') && value.endsWith(']')) {
|
|
144
|
+
value = value.slice(1, -1).split(',').map((v) => {
|
|
145
|
+
const trimmed = v.trim();
|
|
146
|
+
// Strip quotes from array values
|
|
147
|
+
if ((trimmed.startsWith("'") && trimmed.endsWith("'")) ||
|
|
148
|
+
(trimmed.startsWith('"') && trimmed.endsWith('"'))) {
|
|
149
|
+
return trimmed.slice(1, -1);
|
|
150
|
+
}
|
|
151
|
+
return trimmed;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
if (currentSubsection && currentSection) {
|
|
155
|
+
contract[currentSection][currentSubsection][key.trim()] = value;
|
|
156
|
+
}
|
|
157
|
+
else if (currentSection) {
|
|
158
|
+
contract[currentSection][key.trim()] = value;
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
contract[key.trim()] = value;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return contract;
|
|
166
|
+
}
|
|
167
|
+
function validateContract(contract) {
|
|
168
|
+
const errors = [];
|
|
169
|
+
// Check guardian section
|
|
170
|
+
if (!contract.guardian) {
|
|
171
|
+
errors.push('Missing "guardian" section');
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
if (typeof contract.guardian.enabled !== 'boolean') {
|
|
175
|
+
errors.push('guardian.enabled must be true or false');
|
|
176
|
+
}
|
|
177
|
+
if (contract.guardian.enabled && !contract.guardian.schemaFile) {
|
|
178
|
+
errors.push('guardian.schemaFile is required when guardian is enabled');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Check health section
|
|
182
|
+
if (!contract.health) {
|
|
183
|
+
errors.push('Missing "health" section');
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
if (typeof contract.health.enabled !== 'boolean') {
|
|
187
|
+
errors.push('health.enabled must be true or false');
|
|
188
|
+
}
|
|
189
|
+
if (contract.health.enabled && !contract.health.endpoint) {
|
|
190
|
+
errors.push('health.endpoint is required when health is enabled');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Check ci section
|
|
194
|
+
if (!contract.ci) {
|
|
195
|
+
errors.push('Missing "ci" section');
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
if (!contract.ci.provider) {
|
|
199
|
+
errors.push('ci.provider is required (e.g., "github")');
|
|
200
|
+
}
|
|
201
|
+
if (!contract.ci.postDeploy) {
|
|
202
|
+
contract.ci.postDeploy = { enabled: false };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// Check schema section (optional but if present, validate)
|
|
206
|
+
if (contract.schema) {
|
|
207
|
+
if (typeof contract.schema.enabled !== 'boolean') {
|
|
208
|
+
errors.push('schema.enabled must be true or false');
|
|
209
|
+
}
|
|
210
|
+
if (contract.schema.enabled) {
|
|
211
|
+
if (!contract.schema.file) {
|
|
212
|
+
errors.push('schema.file is required when schema is enabled');
|
|
213
|
+
}
|
|
214
|
+
if (!contract.schema.mode) {
|
|
215
|
+
errors.push('schema.mode is required (strict or template_only)');
|
|
216
|
+
}
|
|
217
|
+
if (contract.schema.mode && !['strict', 'template_only'].includes(contract.schema.mode)) {
|
|
218
|
+
errors.push('schema.mode must be "strict" or "template_only"');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
valid: errors.length === 0,
|
|
224
|
+
errors
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
export function getDefaultContract(mode = 'dev') {
|
|
228
|
+
return {
|
|
229
|
+
version: 1,
|
|
230
|
+
mode,
|
|
231
|
+
guardian: {
|
|
232
|
+
enabled: true,
|
|
233
|
+
schemaFile: 'BACKEND_SCHEMA.ts',
|
|
234
|
+
hook: 'husky',
|
|
235
|
+
approvalsTag: 'ARCHITECT_APPROVED'
|
|
236
|
+
},
|
|
237
|
+
health: {
|
|
238
|
+
enabled: mode !== 'solo',
|
|
239
|
+
endpoint: '/api/health',
|
|
240
|
+
failOn: {
|
|
241
|
+
critical: true,
|
|
242
|
+
error: true,
|
|
243
|
+
warning: false
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
ci: {
|
|
247
|
+
provider: 'github',
|
|
248
|
+
branches: ['main'],
|
|
249
|
+
requiredOnPR: true,
|
|
250
|
+
postDeploy: {
|
|
251
|
+
enabled: mode === 'team',
|
|
252
|
+
waitSeconds: 90,
|
|
253
|
+
healthUrlVar: 'CERBER_HEALTH_URL',
|
|
254
|
+
authHeaderSecret: 'CERBER_HEALTH_AUTH_HEADER'
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
schema: {
|
|
258
|
+
enabled: true,
|
|
259
|
+
file: 'BACKEND_SCHEMA.ts',
|
|
260
|
+
mode: 'template_only',
|
|
261
|
+
description: 'Project architecture contract (user-owned)'
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
//# sourceMappingURL=contract-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-parser.js","sourceRoot":"","sources":["../../src/cli/contract-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,aAAa,CAAC;AAC7B,OAAO,IAAI,MAAM,MAAM,CAAC;AAGxB,MAAM,iBAAiB,GAAG,oBAAoB,CAAC;AAC/C,MAAM,qBAAqB,GAAG,SAAS,CAAC;AACxC,MAAM,mBAAmB,GAAG,KAAK,CAAC;AAElC,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,WAAmB;IAC3D,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;IAEvD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACvD,OAAO,eAAe,CAAC,OAAO,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,0BAA0B;QAC1B,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,qBAAqB,EAAE,EAAE,CAAC;IACvE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAElC,+BAA+B;IAC/B,MAAM,kBAAkB,GAAG,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,iBAAiB,CAAC,CAAC;IACtF,IAAI,kBAAkB,KAAK,CAAC,CAAC,EAAE,CAAC;QAC9B,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE;gBACL,OAAO,EAAE,YAAY,iBAAiB,kBAAkB;gBACxD,OAAO,EAAE,iEAAiE;aAC3E;SACF,CAAC;IACJ,CAAC;IAED,uBAAuB;IACvB,IAAI,cAAc,GAAG,CAAC,CAAC,CAAC;IACxB,IAAI,YAAY,GAAG,CAAC,CAAC,CAAC;IAEtB,KAAK,IAAI,CAAC,GAAG,kBAAkB,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvD,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,qBAAqB,CAAC,EAAE,CAAC;YACtD,cAAc,GAAG,CAAC,GAAG,CAAC,CAAC;QACzB,CAAC;aAAM,IAAI,cAAc,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,mBAAmB,EAAE,CAAC;YAC5E,YAAY,GAAG,CAAC,CAAC;YACjB,MAAM;QACR,CAAC;IACH,CAAC;IAED,IAAI,cAAc,KAAK,CAAC,CAAC,EAAE,CAAC;QAC1B,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE;gBACL,OAAO,EAAE,sDAAsD;gBAC/D,IAAI,EAAE,kBAAkB,GAAG,CAAC;gBAC5B,OAAO,EAAE,kCAAkC,kBAAkB,GAAG,CAAC,EAAE;aACpE;SACF,CAAC;IACJ,CAAC;IAED,IAAI,YAAY,KAAK,CAAC,CAAC,EAAE,CAAC;QACxB,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE;gBACL,OAAO,EAAE,0BAA0B;gBACnC,IAAI,EAAE,cAAc;gBACpB,OAAO,EAAE,uCAAuC;aACjD;SACF,CAAC;IACJ,CAAC;IAED,MAAM,WAAW,GAAG,KAAK,CAAC,KAAK,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEzE,kDAAkD;IAClD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,eAAe,CAAC,WAAW,CAAC,CAAC;QAE9C,2BAA2B;QAC3B,MAAM,UAAU,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QAC9C,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;YACtB,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE;oBACL,OAAO,EAAE,4BAA4B;oBACrC,OAAO,EAAE,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;iBACtC;aACF,CAAC;QACJ,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IACrC,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE;gBACL,OAAO,EAAE,+BAA+B;gBACxC,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,wBAAwB;aACjD;SACF,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,WAAmB;IAC1C,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IAElG,MAAM,QAAQ,GAAQ;QACpB,OAAO,EAAE,CAAC;QACV,IAAI,EAAE,KAAK;QACX,QAAQ,EAAE,EAAE;QACZ,MAAM,EAAE,EAAE;QACV,EAAE,EAAE,EAAE;QACN,MAAM,EAAE,EAAE;QACV,IAAI,EAAE,EAAE;KACT,CAAC;IAEF,IAAI,cAAc,GAAkB,IAAI,CAAC;IACzC,IAAI,iBAAiB,GAAkB,IAAI,CAAC;IAE5C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC;QAErD,IAAI,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1C,gBAAgB;YAChB,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YACjC,cAAc,GAAG,GAAG,CAAC;YACrB,iBAAiB,GAAG,IAAI,CAAC;QAC3B,CAAC;aAAM,IAAI,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACjD,mBAAmB;YACnB,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YACjC,iBAAiB,GAAG,GAAG,CAAC;YACxB,IAAI,cAAc,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;gBACrD,QAAQ,CAAC,cAAc,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;YACrC,CAAC;QACH,CAAC;aAAM,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACjC,iBAAiB;YACjB,MAAM,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAChD,IAAI,KAAK,GAAQ,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;YAE7C,wCAAwC;YACxC,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACxC,IAAI,YAAY,KAAK,CAAC,CAAC,EAAE,CAAC;gBACxB,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,IAAI,EAAE,CAAC;YAClD,CAAC;YAED,mBAAmB;YACnB,IAAI,KAAK,KAAK,MAAM;gBAAE,KAAK,GAAG,IAAI,CAAC;iBAC9B,IAAI,KAAK,KAAK,OAAO;gBAAE,KAAK,GAAG,KAAK,CAAC;iBACrC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBAAE,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;iBACjD,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBACtD,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAS,EAAE,EAAE;oBACtD,MAAM,OAAO,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;oBACzB,iCAAiC;oBACjC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;wBAClD,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;wBACvD,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;oBAC9B,CAAC;oBACD,OAAO,OAAO,CAAC;gBACjB,CAAC,CAAC,CAAC;YACL,CAAC;YAED,IAAI,iBAAiB,IAAI,cAAc,EAAE,CAAC;gBACxC,QAAQ,CAAC,cAAc,CAAC,CAAC,iBAAiB,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,KAAK,CAAC;YAClE,CAAC;iBAAM,IAAI,cAAc,EAAE,CAAC;gBAC1B,QAAQ,CAAC,cAAc,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,KAAK,CAAC;YAC/C,CAAC;iBAAM,CAAC;gBACN,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,KAAK,CAAC;YAC/B,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,QAA0B,CAAC;AACpC,CAAC;AAED,SAAS,gBAAgB,CAAC,QAAa;IACrC,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,yBAAyB;IACzB,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;QACvB,MAAM,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;IAC5C,CAAC;SAAM,CAAC;QACN,IAAI,OAAO,QAAQ,CAAC,QAAQ,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YACnD,MAAM,CAAC,IAAI,CAAC,wCAAwC,CAAC,CAAC;QACxD,CAAC;QACD,IAAI,QAAQ,CAAC,QAAQ,CAAC,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;YAC/D,MAAM,CAAC,IAAI,CAAC,0DAA0D,CAAC,CAAC;QAC1E,CAAC;IACH,CAAC;IAED,uBAAuB;IACvB,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QACrB,MAAM,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;IAC1C,CAAC;SAAM,CAAC;QACN,IAAI,OAAO,QAAQ,CAAC,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YACjD,MAAM,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAC;QACtD,CAAC;QACD,IAAI,QAAQ,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YACzD,MAAM,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;IAED,mBAAmB;IACnB,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IACtC,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC;YAC1B,MAAM,CAAC,IAAI,CAAC,0CAA0C,CAAC,CAAC;QAC1D,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC;YAC5B,QAAQ,CAAC,EAAE,CAAC,UAAU,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAC9C,CAAC;IACH,CAAC;IAED,2DAA2D;IAC3D,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;QACpB,IAAI,OAAO,QAAQ,CAAC,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YACjD,MAAM,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAC;QACtD,CAAC;QACD,IAAI,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YAC5B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;gBAC1B,MAAM,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;YAChE,CAAC;YACD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;gBAC1B,MAAM,CAAC,IAAI,CAAC,mDAAmD,CAAC,CAAC;YACnE,CAAC;YACD,IAAI,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;gBACxF,MAAM,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC;YACjE,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,MAAM,KAAK,CAAC;QAC1B,MAAM;KACP,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,OAAgC,KAAK;IACtE,OAAO;QACL,OAAO,EAAE,CAAC;QACV,IAAI;QACJ,QAAQ,EAAE;YACR,OAAO,EAAE,IAAI;YACb,UAAU,EAAE,mBAAmB;YAC/B,IAAI,EAAE,OAAO;YACb,YAAY,EAAE,oBAAoB;SACnC;QACD,MAAM,EAAE;YACN,OAAO,EAAE,IAAI,KAAK,MAAM;YACxB,QAAQ,EAAE,aAAa;YACvB,MAAM,EAAE;gBACN,QAAQ,EAAE,IAAI;gBACd,KAAK,EAAE,IAAI;gBACX,OAAO,EAAE,KAAK;aACf;SACF;QACD,EAAE,EAAE;YACF,QAAQ,EAAE,QAAQ;YAClB,QAAQ,EAAE,CAAC,MAAM,CAAC;YAClB,YAAY,EAAE,IAAI;YAClB,UAAU,EAAE;gBACV,OAAO,EAAE,IAAI,KAAK,MAAM;gBACxB,WAAW,EAAE,EAAE;gBACf,YAAY,EAAE,mBAAmB;gBACjC,gBAAgB,EAAE,2BAA2B;aAC9C;SACF;QACD,MAAM,EAAE;YACN,OAAO,EAAE,IAAI;YACb,IAAI,EAAE,mBAAmB;YACzB,IAAI,EAAE,eAAe;YACrB,WAAW,EAAE,4CAA4C;SAC1D;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cerber init command
|
|
3
|
+
*
|
|
4
|
+
* Initializes Cerber in client project with instant setup
|
|
5
|
+
*
|
|
6
|
+
* @author Stefan Pitek
|
|
7
|
+
* @license MIT
|
|
8
|
+
*/
|
|
9
|
+
import { InitOptions } from './types.js';
|
|
10
|
+
export declare function initCommand(options?: InitOptions): Promise<void>;
|
|
11
|
+
//# sourceMappingURL=init.d.ts.map
|
|
@@ -0,0 +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;AAqFxE,wBAAsB,WAAW,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsG1E"}
|
package/dist/cli/init.js
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cerber init command
|
|
3
|
+
*
|
|
4
|
+
* Initializes Cerber in client project with instant setup
|
|
5
|
+
*
|
|
6
|
+
* @author Stefan Pitek
|
|
7
|
+
* @license MIT
|
|
8
|
+
*/
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import fs from 'fs/promises';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import { getDefaultContract, parseCerberContract } from './contract-parser.js';
|
|
13
|
+
import { TemplateGenerator } from './template-generator.js';
|
|
14
|
+
const CERBER_MD_TEMPLATE = `# CERBER.md - Architecture Roadmap
|
|
15
|
+
|
|
16
|
+
> **This is your single source of truth. AI agents and developers enforce this contract.**
|
|
17
|
+
|
|
18
|
+
## CERBER_CONTRACT
|
|
19
|
+
\`\`\`yaml
|
|
20
|
+
version: 1
|
|
21
|
+
mode: dev # solo | dev | team
|
|
22
|
+
|
|
23
|
+
guardian:
|
|
24
|
+
enabled: true
|
|
25
|
+
schemaFile: BACKEND_SCHEMA.ts
|
|
26
|
+
hook: husky
|
|
27
|
+
approvalsTag: ARCHITECT_APPROVED
|
|
28
|
+
|
|
29
|
+
health:
|
|
30
|
+
enabled: true
|
|
31
|
+
endpoint: /api/health
|
|
32
|
+
failOn:
|
|
33
|
+
critical: true
|
|
34
|
+
error: true
|
|
35
|
+
warning: false
|
|
36
|
+
|
|
37
|
+
ci:
|
|
38
|
+
provider: github
|
|
39
|
+
branches: [main]
|
|
40
|
+
requiredOnPR: true
|
|
41
|
+
postDeploy:
|
|
42
|
+
enabled: false
|
|
43
|
+
waitSeconds: 90
|
|
44
|
+
healthUrlVar: CERBER_HEALTH_URL
|
|
45
|
+
authHeaderSecret: CERBER_HEALTH_AUTH_HEADER
|
|
46
|
+
|
|
47
|
+
schema:
|
|
48
|
+
enabled: true
|
|
49
|
+
file: BACKEND_SCHEMA.ts
|
|
50
|
+
mode: template_only # strict | template_only
|
|
51
|
+
description: "Project architecture contract (user-owned)"
|
|
52
|
+
# strict = Cerber never creates schema, you must create it
|
|
53
|
+
# template_only = Cerber creates minimal template if missing
|
|
54
|
+
\`\`\`
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 🎯 Architecture Roadmap
|
|
59
|
+
|
|
60
|
+
**Status:** Initial Setup
|
|
61
|
+
|
|
62
|
+
### Phase 1: Foundation (Current)
|
|
63
|
+
- [ ] Setup Guardian pre-commit validation
|
|
64
|
+
- [ ] Configure health checks
|
|
65
|
+
- [ ] Integrate CI/CD pipeline
|
|
66
|
+
|
|
67
|
+
### Phase 2: Production Readiness
|
|
68
|
+
- [ ] Add monitoring
|
|
69
|
+
- [ ] Configure alerting
|
|
70
|
+
- [ ] Load testing
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 📋 Guidelines
|
|
75
|
+
|
|
76
|
+
### Code Organization
|
|
77
|
+
- Routes in \`src/routes/\`
|
|
78
|
+
- Business logic in \`src/services/\`
|
|
79
|
+
- Database schema in \`src/shared/schema.ts\`
|
|
80
|
+
|
|
81
|
+
### Standards
|
|
82
|
+
- TypeScript strict mode
|
|
83
|
+
- ESLint configuration
|
|
84
|
+
- Test coverage > 80%
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 🛡️ Guardian Rules
|
|
89
|
+
|
|
90
|
+
See \`${getDefaultContract().guardian.schemaFile}\` for complete architecture rules.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
*This file is protected by CODEOWNERS. Changes require architect approval.*
|
|
95
|
+
`;
|
|
96
|
+
export async function initCommand(options = {}) {
|
|
97
|
+
const projectRoot = process.cwd();
|
|
98
|
+
// Handle --print-template flag
|
|
99
|
+
if (options.printTemplate) {
|
|
100
|
+
console.log(CERBER_MD_TEMPLATE);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
console.log(chalk.bold.cyan('🛡️ Cerber Core - Project Initialization'));
|
|
104
|
+
console.log('');
|
|
105
|
+
if (options.dryRun) {
|
|
106
|
+
console.log(chalk.yellow('⚠️ DRY RUN MODE - No files will be created'));
|
|
107
|
+
console.log('');
|
|
108
|
+
}
|
|
109
|
+
// Step 1: Check for CERBER.md
|
|
110
|
+
const cerberPath = path.join(projectRoot, 'CERBER.md');
|
|
111
|
+
const parseResult = await parseCerberContract(projectRoot);
|
|
112
|
+
if (!parseResult.success) {
|
|
113
|
+
const error = parseResult.error;
|
|
114
|
+
// File not found - create template
|
|
115
|
+
if (error.message === 'CERBER.md not found') {
|
|
116
|
+
console.log(chalk.yellow('📄 CERBER.md not found'));
|
|
117
|
+
console.log('Creating template...');
|
|
118
|
+
console.log('');
|
|
119
|
+
if (!options.dryRun) {
|
|
120
|
+
await fs.writeFile(cerberPath, CERBER_MD_TEMPLATE, 'utf-8');
|
|
121
|
+
console.log(chalk.green('✅ Created CERBER.md'));
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
console.log(chalk.gray('[DRY RUN] Would create CERBER.md'));
|
|
125
|
+
}
|
|
126
|
+
console.log('');
|
|
127
|
+
console.log(chalk.bold('📝 Next Steps:'));
|
|
128
|
+
console.log('1. Edit CERBER.md and customize the contract for your project');
|
|
129
|
+
console.log('2. Set your desired mode: solo | dev | team');
|
|
130
|
+
console.log('3. Run npx cerber init again to generate files');
|
|
131
|
+
console.log('');
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
// Invalid contract - show detailed error
|
|
135
|
+
console.error(chalk.red('❌ Failed to parse CERBER.md'));
|
|
136
|
+
console.error('');
|
|
137
|
+
console.error(chalk.yellow('Error:'), error.message);
|
|
138
|
+
if (error.line) {
|
|
139
|
+
console.error(chalk.gray(`Line ${error.line}`));
|
|
140
|
+
}
|
|
141
|
+
if (error.context) {
|
|
142
|
+
console.error('');
|
|
143
|
+
console.error(chalk.gray('Expected format:'));
|
|
144
|
+
console.error(chalk.gray(error.context));
|
|
145
|
+
}
|
|
146
|
+
console.error('');
|
|
147
|
+
console.error(chalk.blue('💡 Tip: Run'), chalk.cyan('npx cerber init'), chalk.blue('in an empty repo to see a valid template'));
|
|
148
|
+
console.error('');
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
let contract = parseResult.contract;
|
|
152
|
+
// Step 2: Override mode if specified
|
|
153
|
+
if (options.mode) {
|
|
154
|
+
console.log(chalk.blue(`📋 Overriding mode: ${contract.mode} → ${options.mode}`));
|
|
155
|
+
contract.mode = options.mode;
|
|
156
|
+
}
|
|
157
|
+
console.log(chalk.bold(`📋 Contract found:`));
|
|
158
|
+
console.log(` Mode: ${chalk.cyan(contract.mode)}`);
|
|
159
|
+
console.log(` Guardian: ${contract.guardian.enabled ? chalk.green('enabled') : chalk.gray('disabled')}`);
|
|
160
|
+
console.log(` Health: ${contract.health.enabled ? chalk.green('enabled') : chalk.gray('disabled')}`);
|
|
161
|
+
console.log(` CI: ${contract.ci.provider}`);
|
|
162
|
+
if (contract.ci.postDeploy.enabled) {
|
|
163
|
+
console.log(` Post-deploy gate: ${chalk.green('enabled')} → ${contract.ci.postDeploy.healthUrlVar}`);
|
|
164
|
+
}
|
|
165
|
+
console.log('');
|
|
166
|
+
// Step 3: Generate files
|
|
167
|
+
console.log(chalk.bold('🔧 Generating files...'));
|
|
168
|
+
console.log('');
|
|
169
|
+
const generator = new TemplateGenerator(projectRoot, contract, options);
|
|
170
|
+
const files = await generator.generateAll();
|
|
171
|
+
await generator.writeFiles(files);
|
|
172
|
+
// Step 4: Update package.json
|
|
173
|
+
if (!options.dryRun && contract.guardian.enabled) {
|
|
174
|
+
await updatePackageJson(projectRoot);
|
|
175
|
+
}
|
|
176
|
+
console.log('');
|
|
177
|
+
console.log(chalk.bold.green('✅ Cerber initialization complete!'));
|
|
178
|
+
console.log('');
|
|
179
|
+
// Step 5: Show next steps
|
|
180
|
+
await showNextSteps(contract, options, files, projectRoot);
|
|
181
|
+
}
|
|
182
|
+
async function updatePackageJson(projectRoot) {
|
|
183
|
+
const packagePath = path.join(projectRoot, 'package.json');
|
|
184
|
+
try {
|
|
185
|
+
const content = await fs.readFile(packagePath, 'utf-8');
|
|
186
|
+
const pkg = JSON.parse(content);
|
|
187
|
+
if (!pkg.scripts) {
|
|
188
|
+
pkg.scripts = {};
|
|
189
|
+
}
|
|
190
|
+
if (!pkg.scripts['cerber:guardian']) {
|
|
191
|
+
pkg.scripts['cerber:guardian'] = 'node scripts/cerber-guardian.mjs';
|
|
192
|
+
await fs.writeFile(packagePath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
|
|
193
|
+
console.log(chalk.green('✅ Updated package.json scripts'));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
console.log(chalk.yellow('⚠️ Could not update package.json (file may not exist)'));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
async function fileExists(filePath) {
|
|
201
|
+
try {
|
|
202
|
+
await fs.access(filePath);
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async function showNextSteps(contract, options, generatedFiles, projectRoot) {
|
|
210
|
+
console.log(chalk.bold('📝 Next Steps:'));
|
|
211
|
+
console.log('');
|
|
212
|
+
if (contract.guardian.enabled && !options.noHusky) {
|
|
213
|
+
console.log(chalk.cyan('1. Install Husky (if not already installed):'));
|
|
214
|
+
console.log(' npm install husky --save-dev');
|
|
215
|
+
console.log(' npx husky install');
|
|
216
|
+
console.log('');
|
|
217
|
+
}
|
|
218
|
+
// Schema section (based on schema config in CERBER.md)
|
|
219
|
+
if (contract.schema && contract.schema.enabled) {
|
|
220
|
+
const schemaFile = contract.schema.file;
|
|
221
|
+
const schemaGenerated = generatedFiles.some(f => f.path.endsWith(schemaFile));
|
|
222
|
+
const schemaExists = await fileExists(path.join(projectRoot, schemaFile));
|
|
223
|
+
if (schemaGenerated) {
|
|
224
|
+
// Template was generated
|
|
225
|
+
console.log(chalk.cyan(`2. Customize your architecture schema:`));
|
|
226
|
+
console.log(chalk.yellow(` ⚠️ ${schemaFile} is a TEMPLATE (not source of truth)`));
|
|
227
|
+
console.log(` Edit it to match your architecture defined in CERBER.md`);
|
|
228
|
+
console.log(' See: https://github.com/Agaslez/cerber-core#guardian-configuration');
|
|
229
|
+
}
|
|
230
|
+
else if (!schemaExists && contract.schema.mode === 'strict') {
|
|
231
|
+
// Strict mode: schema required but not generated
|
|
232
|
+
console.log(chalk.cyan(`2. Create your schema file:`));
|
|
233
|
+
console.log(chalk.yellow(` Schema mode: strict (you must create ${schemaFile})`));
|
|
234
|
+
console.log(` Template: npx cerber init --print-schema-template > ${schemaFile}`);
|
|
235
|
+
console.log(' See: https://github.com/Agaslez/cerber-core#guardian-configuration');
|
|
236
|
+
}
|
|
237
|
+
else if (schemaExists) {
|
|
238
|
+
// Schema exists, don't mention creating it
|
|
239
|
+
console.log(chalk.green(`✅ Schema file exists: ${schemaFile}`));
|
|
240
|
+
}
|
|
241
|
+
console.log('');
|
|
242
|
+
}
|
|
243
|
+
else if (contract.guardian.enabled) {
|
|
244
|
+
// Legacy: guardian enabled but no schema config (backward compat)
|
|
245
|
+
const schemaGenerated = generatedFiles.some(f => f.path.endsWith(contract.guardian.schemaFile));
|
|
246
|
+
if (schemaGenerated) {
|
|
247
|
+
console.log(chalk.cyan(`2. Customize your architecture schema:`));
|
|
248
|
+
console.log(` Edit: ${contract.guardian.schemaFile}`);
|
|
249
|
+
console.log(' Add your project-specific rules and patterns');
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
console.log(chalk.cyan(`2. Create your schema file: ${contract.guardian.schemaFile}`));
|
|
253
|
+
}
|
|
254
|
+
console.log(' See: https://github.com/Agaslez/cerber-core#guardian-configuration');
|
|
255
|
+
console.log('');
|
|
256
|
+
}
|
|
257
|
+
if (contract.health.enabled && !options.noHealth) {
|
|
258
|
+
console.log(chalk.cyan('3. Customize health checks:'));
|
|
259
|
+
console.log(' Edit: src/cerber/health-checks.ts');
|
|
260
|
+
console.log(' Add route to your server: src/cerber/health-route.ts');
|
|
261
|
+
console.log(` Endpoint: ${contract.health.endpoint}`);
|
|
262
|
+
console.log('');
|
|
263
|
+
}
|
|
264
|
+
if (contract.ci.provider === 'github' && !options.noWorkflow) {
|
|
265
|
+
console.log(chalk.cyan('4. GitHub Actions workflow created:'));
|
|
266
|
+
console.log(' .github/workflows/cerber.yml');
|
|
267
|
+
if (contract.ci.postDeploy.enabled) {
|
|
268
|
+
console.log('');
|
|
269
|
+
console.log(chalk.yellow(' ⚠️ Post-deploy health check requires:'));
|
|
270
|
+
console.log(` - GitHub Variable: ${contract.ci.postDeploy.healthUrlVar}`);
|
|
271
|
+
console.log(' - Optional Secret: CERBER_HEALTH_AUTH_HEADER');
|
|
272
|
+
console.log(' - Set in: Settings > Secrets and variables > Actions > Variables');
|
|
273
|
+
console.log(` - Example: https://your-api.com${contract.health.endpoint}`);
|
|
274
|
+
}
|
|
275
|
+
console.log(' Required check name: Cerber CI / job: cerber-ci');
|
|
276
|
+
console.log('');
|
|
277
|
+
}
|
|
278
|
+
if (contract.mode === 'team') {
|
|
279
|
+
console.log(chalk.cyan('5. Team mode setup:'));
|
|
280
|
+
console.log(' - Edit .github/CODEOWNERS (replace @OWNER_USERNAME)');
|
|
281
|
+
console.log(' - Enable branch protection: Settings > Branches');
|
|
282
|
+
console.log(' - Require review from Code Owners');
|
|
283
|
+
console.log(' - Set required checks to: Cerber CI');
|
|
284
|
+
console.log('');
|
|
285
|
+
}
|
|
286
|
+
console.log(chalk.bold.green('🚀 Ready to commit!'));
|
|
287
|
+
console.log(' git add .');
|
|
288
|
+
console.log(' git commit -m "feat: add Cerber protection"');
|
|
289
|
+
console.log('');
|
|
290
|
+
console.log(chalk.gray('Need help? https://github.com/Agaslez/cerber-core/discussions'));
|
|
291
|
+
}
|
|
292
|
+
//# sourceMappingURL=init.js.map
|