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.
Files changed (42) hide show
  1. package/CHANGELOG.md +158 -68
  2. package/README.md +198 -5
  3. package/USAGE_GUIDE.md +254 -0
  4. package/bin/cerber +120 -104
  5. package/dev/templates/BACKEND_SCHEMA.ts.tpl +48 -0
  6. package/dev/templates/cerber-guardian.mjs.tpl +44 -0
  7. package/dev/templates/cerber.yml.tpl +53 -0
  8. package/dev/templates/health-checks.ts.tpl +11 -0
  9. package/dev/templates/health-route.ts.tpl +50 -0
  10. package/dev/templates/pre-commit.tpl +4 -0
  11. package/dist/cli/contract-parser.d.ts +13 -0
  12. package/dist/cli/contract-parser.d.ts.map +1 -0
  13. package/dist/cli/contract-parser.js +265 -0
  14. package/dist/cli/contract-parser.js.map +1 -0
  15. package/dist/cli/init.d.ts +11 -0
  16. package/dist/cli/init.d.ts.map +1 -0
  17. package/dist/cli/init.js +292 -0
  18. package/dist/cli/init.js.map +1 -0
  19. package/dist/cli/template-generator.d.ts +29 -0
  20. package/dist/cli/template-generator.d.ts.map +1 -0
  21. package/dist/cli/template-generator.js +252 -0
  22. package/dist/cli/template-generator.js.map +1 -0
  23. package/dist/cli/types.d.ts +79 -0
  24. package/dist/cli/types.d.ts.map +1 -0
  25. package/dist/cli/types.js +8 -0
  26. package/dist/cli/types.js.map +1 -0
  27. package/examples/backend-schema.ts +9 -2
  28. package/examples/frontend-schema.ts +9 -2
  29. package/package.json +106 -104
  30. package/solo/templates/BACKEND_SCHEMA.ts.tpl +48 -0
  31. package/solo/templates/cerber-guardian.mjs.tpl +44 -0
  32. package/solo/templates/cerber.yml.tpl +53 -0
  33. package/solo/templates/health-checks.ts.tpl +29 -0
  34. package/solo/templates/health-route.ts.tpl +50 -0
  35. package/solo/templates/pre-commit.tpl +4 -0
  36. package/team/templates/BACKEND_SCHEMA.ts.tpl +48 -0
  37. package/team/templates/CODEOWNERS.tpl +6 -0
  38. package/team/templates/cerber-guardian.mjs.tpl +44 -0
  39. package/team/templates/cerber.yml.tpl +53 -0
  40. package/team/templates/health-checks.ts.tpl +10 -0
  41. package/team/templates/health-route.ts.tpl +50 -0
  42. 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,4 @@
1
+ #!/bin/sh
2
+ # Generated by Cerber init
3
+
4
+ npm run cerber:guardian
@@ -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":"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"}
@@ -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"}
@@ -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