@kabran-tecnologia/kabran-config 1.10.0 → 2.0.0

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.
@@ -0,0 +1,305 @@
1
+ /**
2
+ * Kabran Config Loader
3
+ *
4
+ * Loads project configuration from kabran.config.mjs/js/json with fallback to defaults.
5
+ * Enables projects to customize validator behavior without modifying kabran-config.
6
+ *
7
+ * Features:
8
+ * - Smart detection of tools (ESLint, TypeScript, Prettier, Vitest, Playwright)
9
+ * - Automatic Doppler integration for secrets injection in tests
10
+ * - Convention over configuration - works out of the box
11
+ *
12
+ * @module config-loader
13
+ */
14
+
15
+ import {existsSync, readFileSync} from 'node:fs';
16
+ import {readFile} from 'node:fs/promises';
17
+ import {join} from 'node:path';
18
+ import {pathToFileURL} from 'node:url';
19
+ import {execSync} from 'node:child_process';
20
+
21
+ /**
22
+ * Default configuration values.
23
+ * These match the current hardcoded values in validators to ensure backwards compatibility.
24
+ */
25
+ export const DEFAULTS = {
26
+ readme: {
27
+ required: ['Installation', 'Usage', 'License'],
28
+ recommended: ['Development', 'Testing', 'Contributing'],
29
+ },
30
+ env: {
31
+ requireExample: true,
32
+ detectPatterns: ['process.env', 'import.meta.env', 'Deno.env', 'os.getenv', '$_ENV'],
33
+ },
34
+ quality: {
35
+ standardPath: 'docs/quality/001-quality-standard.md',
36
+ },
37
+ };
38
+
39
+ /**
40
+ * Config file patterns for each tool.
41
+ */
42
+ const TOOL_CONFIGS = {
43
+ eslint: ['eslint.config.mjs', 'eslint.config.js', 'eslint.config.cjs', '.eslintrc.js', '.eslintrc.json', '.eslintrc.cjs'],
44
+ typescript: ['tsconfig.json'],
45
+ prettier: ['prettier.config.mjs', 'prettier.config.js', '.prettierrc', '.prettierrc.json', '.prettierrc.js'],
46
+ vitest: ['vitest.config.ts', 'vitest.config.mts', 'vitest.config.js', 'vitest.config.mjs'],
47
+ playwright: ['playwright.config.ts', 'playwright.config.js'],
48
+ };
49
+
50
+ /**
51
+ * Check if any of the config files exist for a tool.
52
+ * @param {string} cwd - Working directory
53
+ * @param {string[]} configFiles - List of possible config file names
54
+ * @returns {boolean} True if any config file exists
55
+ */
56
+ function hasToolConfig(cwd, configFiles) {
57
+ return configFiles.some(file => existsSync(join(cwd, file)));
58
+ }
59
+
60
+ /**
61
+ * Check if Doppler is configured for the current directory.
62
+ * @param {string} cwd - Working directory
63
+ * @returns {boolean} True if Doppler token is configured
64
+ */
65
+ function hasDopplerConfigured(cwd) {
66
+ try {
67
+ const result = execSync('doppler configure get token', {
68
+ cwd,
69
+ stdio: 'pipe',
70
+ encoding: 'utf-8',
71
+ timeout: 5000,
72
+ });
73
+ return result.trim().length > 0;
74
+ } catch {
75
+ return false;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Wrap command with Doppler if configured.
81
+ * @param {string} cmd - Command to wrap
82
+ * @param {boolean} useDoppler - Whether to use Doppler
83
+ * @returns {string} Wrapped command
84
+ */
85
+ export function wrapWithDoppler(cmd, useDoppler) {
86
+ return useDoppler ? `doppler run -- ${cmd}` : cmd;
87
+ }
88
+
89
+ /**
90
+ * Detect available tools and generate smart defaults.
91
+ * Only configures commands for tools that are actually set up in the project.
92
+ * @param {string} cwd - Working directory
93
+ * @returns {object} Detected defaults for CLI commands
94
+ */
95
+ export function detectToolDefaults(cwd) {
96
+ const defaults = {
97
+ check: {},
98
+ test: {},
99
+ build: {},
100
+ ci: { steps: [] },
101
+ };
102
+
103
+ const hasDoppler = hasDopplerConfigured(cwd);
104
+
105
+ // L1: Static Analysis
106
+ if (hasToolConfig(cwd, TOOL_CONFIGS.eslint)) {
107
+ defaults.check.lint = 'npx eslint .';
108
+ }
109
+
110
+ if (hasToolConfig(cwd, TOOL_CONFIGS.typescript)) {
111
+ defaults.check.types = 'npx tsc --noEmit';
112
+ }
113
+
114
+ if (hasToolConfig(cwd, TOOL_CONFIGS.prettier)) {
115
+ defaults.check.format = 'npx prettier --check .';
116
+ }
117
+
118
+ // L2: Unit Tests (with Doppler support)
119
+ if (hasToolConfig(cwd, TOOL_CONFIGS.vitest)) {
120
+ const cmd = 'npx vitest run';
121
+ defaults.test.unit = {
122
+ command: wrapWithDoppler(cmd, hasDoppler),
123
+ doppler: hasDoppler,
124
+ };
125
+ }
126
+
127
+ // L3: Integration Tests (with Doppler support)
128
+ const integrationConfig = join(cwd, 'vitest.integration.config.ts');
129
+ if (existsSync(integrationConfig)) {
130
+ const cmd = 'npx vitest run --config vitest.integration.config.ts';
131
+ defaults.test.integration = {
132
+ command: wrapWithDoppler(cmd, hasDoppler),
133
+ doppler: hasDoppler,
134
+ };
135
+ }
136
+
137
+ // L4: E2E Tests (with Doppler support)
138
+ if (hasToolConfig(cwd, TOOL_CONFIGS.playwright)) {
139
+ const cmd = 'npx playwright test';
140
+ defaults.test.e2e = {
141
+ command: wrapWithDoppler(cmd, hasDoppler),
142
+ doppler: hasDoppler,
143
+ };
144
+ }
145
+
146
+ // Build - check if npm run build exists in package.json
147
+ const packageJsonPath = join(cwd, 'package.json');
148
+ if (existsSync(packageJsonPath)) {
149
+ try {
150
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
151
+ if (packageJson.scripts?.build) {
152
+ defaults.build.command = 'npm run build';
153
+ }
154
+ } catch {
155
+ // Ignore parse errors
156
+ }
157
+ }
158
+
159
+ // CI steps - add detected tools
160
+ if (Object.keys(defaults.check).length > 0) {
161
+ defaults.ci.steps.push('check');
162
+ }
163
+ if (defaults.test.unit) {
164
+ defaults.ci.steps.push('test:unit');
165
+ }
166
+ if (defaults.build.command) {
167
+ defaults.ci.steps.push('build');
168
+ }
169
+
170
+ return defaults;
171
+ }
172
+
173
+ /**
174
+ * Configuration file names to search for, in priority order.
175
+ */
176
+ const CONFIG_FILES = ['kabran.config.mjs', 'kabran.config.js', 'kabran.config.json'];
177
+
178
+ /**
179
+ * Deep merge two objects. Arrays are replaced, not merged.
180
+ * @param {object} target - Base object
181
+ * @param {object} source - Object to merge in
182
+ * @returns {object} Merged object
183
+ */
184
+ function deepMerge(target, source) {
185
+ const result = {...target};
186
+
187
+ for (const key of Object.keys(source)) {
188
+ const sourceValue = source[key];
189
+ const targetValue = target[key];
190
+
191
+ if (sourceValue === null || sourceValue === undefined) {
192
+ continue;
193
+ }
194
+
195
+ if (
196
+ typeof sourceValue === 'object' &&
197
+ !Array.isArray(sourceValue) &&
198
+ typeof targetValue === 'object' &&
199
+ !Array.isArray(targetValue)
200
+ ) {
201
+ result[key] = deepMerge(targetValue, sourceValue);
202
+ } else {
203
+ result[key] = sourceValue;
204
+ }
205
+ }
206
+
207
+ return result;
208
+ }
209
+
210
+ /**
211
+ * Load JSON configuration file.
212
+ * @param {string} configPath - Path to JSON config file
213
+ * @returns {Promise<object>} Parsed configuration
214
+ */
215
+ async function loadJsonConfig(configPath) {
216
+ const content = await readFile(configPath, 'utf-8');
217
+ return JSON.parse(content);
218
+ }
219
+
220
+ /**
221
+ * Load JavaScript/ESM configuration file.
222
+ * @param {string} configPath - Path to JS/MJS config file
223
+ * @returns {Promise<object>} Parsed configuration
224
+ */
225
+ async function loadJsConfig(configPath) {
226
+ const configUrl = pathToFileURL(configPath).href;
227
+ const module = await import(configUrl);
228
+ return module.default || module;
229
+ }
230
+
231
+ /**
232
+ * Load project configuration with fallback to defaults.
233
+ *
234
+ * Configuration priority (highest to lowest):
235
+ * 1. Project config (kabran.config.mjs/js/json)
236
+ * 2. Detected tool defaults (based on existing config files)
237
+ * 3. Static defaults (validators)
238
+ *
239
+ * Features:
240
+ * - Smart detection of ESLint, TypeScript, Prettier, Vitest, Playwright
241
+ * - Automatic Doppler integration for secrets in tests
242
+ * - Project-specific overrides take precedence
243
+ *
244
+ * @param {string} [cwd=process.cwd()] - Directory to search for config
245
+ * @returns {Promise<object>} Merged configuration
246
+ *
247
+ * @example
248
+ * // Zero-config: auto-detects tools in project
249
+ * const config = await loadConfig();
250
+ * // If project has vitest.config.ts, config.test.unit.command is set
251
+ *
252
+ * @example
253
+ * // With custom config in kabran.config.mjs (overrides detected defaults):
254
+ * // export default { test: { unit: { command: 'npm test' } } }
255
+ * const config = await loadConfig('/path/to/project');
256
+ */
257
+ export async function loadConfig(cwd = process.cwd()) {
258
+ // 1. Start with static defaults (validators)
259
+ let config = {...DEFAULTS};
260
+
261
+ // 2. Merge detected tool defaults (CLI commands)
262
+ const toolDefaults = detectToolDefaults(cwd);
263
+ config = deepMerge(config, toolDefaults);
264
+
265
+ // 3. Merge project-specific configuration (highest priority)
266
+ for (const fileName of CONFIG_FILES) {
267
+ const configPath = join(cwd, fileName);
268
+
269
+ if (existsSync(configPath)) {
270
+ try {
271
+ let projectConfig;
272
+
273
+ if (fileName.endsWith('.json')) {
274
+ projectConfig = await loadJsonConfig(configPath);
275
+ } else {
276
+ projectConfig = await loadJsConfig(configPath);
277
+ }
278
+
279
+ return deepMerge(config, projectConfig);
280
+ } catch (error) {
281
+ // Log warning but continue with detected defaults
282
+ console.warn(`Warning: Failed to load ${fileName}: ${error.message}`);
283
+ }
284
+ }
285
+ }
286
+
287
+ return config;
288
+ }
289
+
290
+ /**
291
+ * Check if a config file exists in the given directory.
292
+ * @param {string} [cwd=process.cwd()] - Directory to check
293
+ * @returns {{exists: boolean, path?: string, name?: string}} Config file info
294
+ */
295
+ export function findConfigFile(cwd = process.cwd()) {
296
+ for (const fileName of CONFIG_FILES) {
297
+ const configPath = join(cwd, fileName);
298
+
299
+ if (existsSync(configPath)) {
300
+ return {exists: true, path: configPath, name: fileName};
301
+ }
302
+ }
303
+
304
+ return {exists: false};
305
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "https://kabran.dev/schemas/ci-result.v2.json",
3
+ "$id": "ci-result.v2.json",
4
4
  "title": "CI Result Schema v2",
5
5
  "description": "Unified schema for CI/CD results with quality metrics, timing, and observability data",
6
6
  "type": "object",
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bash
2
2
  # ==============================================================================
3
3
  # Kabran CI Core - Shared Functions
4
- # Part of @kabran-owner/kabran-config
4
+ # Part of @kabran-tecnologia/kabran-config
5
5
  # ==============================================================================
6
6
 
7
7
  # Version
@@ -51,7 +51,7 @@ export function extractHistoryEntry(result) {
51
51
  export function loadHistory(filePath) {
52
52
  if (!existsSync(filePath)) {
53
53
  return {
54
- $schema: 'https://kabran.dev/schemas/ci-result-history.json',
54
+ $schema: 'ci-result-history.json',
55
55
  version: '1.0.0',
56
56
  meta: {
57
57
  created_at: new Date().toISOString(),
@@ -75,7 +75,7 @@ export function loadHistory(filePath) {
75
75
  } catch (error) {
76
76
  // If file is corrupted, start fresh
77
77
  return {
78
- $schema: 'https://kabran.dev/schemas/ci-result-history.json',
78
+ $schema: 'ci-result-history.json',
79
79
  version: '1.0.0',
80
80
  meta: {
81
81
  created_at: new Date().toISOString(),
@@ -436,7 +436,7 @@ export function createMinimalResult({ projectName, passed = true }) {
436
436
  const now = new Date().toISOString()
437
437
 
438
438
  return {
439
- $schema: 'https://kabran.dev/schemas/ci-result.v2.json',
439
+ $schema: 'ci-result.v2.json',
440
440
  version: '1.0.0',
441
441
  meta: {
442
442
  generated_at: now,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bash
2
2
  # ==============================================================================
3
3
  # Kabran Deploy Core - Shared Functions
4
- # Part of @kabran-owner/kabran-config
4
+ # Part of @kabran-tecnologia/kabran-config
5
5
  # ==============================================================================
6
6
 
7
7
  # ==============================================================================
@@ -18,9 +18,13 @@ import {exec} from 'node:child_process';
18
18
  import {promisify} from 'node:util';
19
19
  import fs from 'node:fs';
20
20
  import path from 'node:path';
21
+ import {loadConfig, DEFAULTS} from '../core/config-loader.mjs';
21
22
 
22
23
  const execAsync = promisify(exec);
23
24
 
25
+ // Default patterns for backwards compatibility
26
+ export const DEFAULT_DETECT_PATTERNS = DEFAULTS.env.detectPatterns;
27
+
24
28
  /**
25
29
  * Check if .env file is tracked in git (CRITICAL SECURITY ISSUE)
26
30
  * @param {string} [cwd] - Directory to check (defaults to process.cwd())
@@ -40,17 +44,10 @@ export async function checkEnvInGit(cwd = process.cwd()) {
40
44
  /**
41
45
  * Detect if project uses environment variables
42
46
  * @param {string} [cwd] - Directory to check (defaults to process.cwd())
47
+ * @param {string[]} [patterns] - Patterns to search for (defaults to config patterns)
43
48
  * @returns {Promise<{usesEnv: boolean, files: string[]}>}
44
49
  */
45
- export async function detectEnvUsage(cwd = process.cwd()) {
46
- const patterns = [
47
- 'process.env', // Node.js
48
- 'os.getenv', // Python
49
- 'import.meta.env', // Vite/ESM
50
- 'Deno.env', // Deno
51
- '$_ENV', // PHP
52
- ];
53
-
50
+ export async function detectEnvUsage(cwd = process.cwd(), patterns = DEFAULT_DETECT_PATTERNS) {
54
51
  const extensions = ['js', 'ts', 'jsx', 'tsx', 'mjs', 'cjs', 'py', 'php'];
55
52
  const extensionPattern = extensions.join(',');
56
53
 
@@ -157,6 +154,9 @@ export async function validateEnv(cwd = process.cwd(), silent = false) {
157
154
  const errors = [];
158
155
  const warnings = [];
159
156
 
157
+ // Load project config
158
+ const config = await loadConfig(cwd);
159
+
160
160
  // CRITICAL: Check if .env is committed to git
161
161
  log('Checking for .env in git...');
162
162
  const envInGit = await checkEnvInGit(cwd);
@@ -171,7 +171,7 @@ export async function validateEnv(cwd = process.cwd(), silent = false) {
171
171
 
172
172
  // Detect if project uses environment variables
173
173
  log('Detecting environment variable usage...');
174
- const {usesEnv, files} = await detectEnvUsage(cwd);
174
+ const {usesEnv, files} = await detectEnvUsage(cwd, config.env.detectPatterns);
175
175
 
176
176
  if (!usesEnv) {
177
177
  log(' No environment variable usage detected');
@@ -236,9 +236,10 @@ export async function validateEnv(cwd = process.cwd(), silent = false) {
236
236
  * @returns {Promise<Object>} Check result for ci-result.json
237
237
  */
238
238
  export async function getEnvCheckResult(cwd = process.cwd()) {
239
+ const config = await loadConfig(cwd);
239
240
  const envInGit = await checkEnvInGit(cwd);
240
241
  const envExample = checkEnvExampleExists(cwd);
241
- const {usesEnv} = await detectEnvUsage(cwd);
242
+ const {usesEnv} = await detectEnvUsage(cwd, config.env.detectPatterns);
242
243
 
243
244
  let undocumented = [];
244
245
  if (envExample.exists) {
@@ -103,7 +103,7 @@ async function runValidators(projectRoot, options = {}) {
103
103
 
104
104
  if (!options.skipReadme) {
105
105
  try {
106
- results.readme = getReadmeCheckResult(projectRoot)
106
+ results.readme = await getReadmeCheckResult(projectRoot)
107
107
  } catch (err) {
108
108
  results.readme = { status: 'fail', error: err.message }
109
109
  }
@@ -119,7 +119,7 @@ async function runValidators(projectRoot, options = {}) {
119
119
 
120
120
  if (!options.skipQualityStandard) {
121
121
  try {
122
- results.quality_standard = getQualityStandardCheckResult(projectRoot)
122
+ results.quality_standard = await getQualityStandardCheckResult(projectRoot)
123
123
  } catch (err) {
124
124
  results.quality_standard = { status: 'fail', error: err.message }
125
125
  }
@@ -216,7 +216,7 @@ export function generateCiResult(input) {
216
216
 
217
217
  // Build result object
218
218
  const result = {
219
- $schema: 'https://kabran.dev/schemas/ci-result.v2.json',
219
+ $schema: 'ci-result.v2.json',
220
220
  version: '1.0.0',
221
221
 
222
222
  meta,
@@ -16,6 +16,10 @@
16
16
 
17
17
  import {existsSync, readFileSync} from 'node:fs';
18
18
  import {join} from 'node:path';
19
+ import {loadConfig, DEFAULTS} from '../core/config-loader.mjs';
20
+
21
+ // Default path for backwards compatibility
22
+ export const DEFAULT_STANDARD_PATH = DEFAULTS.quality.standardPath;
19
23
 
20
24
  /**
21
25
  * Required sections that must be present in quality-standard.md
@@ -33,10 +37,11 @@ export const REQUIRED_FRONTMATTER = ['title', 'type', 'status'];
33
37
  /**
34
38
  * Find quality-standard.md file in the project
35
39
  * @param {string} cwd - Current working directory
40
+ * @param {string} [standardPath] - Path to quality standard file (relative to cwd)
36
41
  * @returns {{exists: boolean, path?: string} | null} File info or null
37
42
  */
38
- export function findQualityStandard(cwd = process.cwd()) {
39
- const expectedPath = join(cwd, 'docs', 'quality', '001-quality-standard.md');
43
+ export function findQualityStandard(cwd = process.cwd(), standardPath = DEFAULT_STANDARD_PATH) {
44
+ const expectedPath = join(cwd, standardPath);
40
45
 
41
46
  if (existsSync(expectedPath)) {
42
47
  return {
@@ -183,26 +188,30 @@ export function compareOverrides(documented, code) {
183
188
  * Validate quality-standard.md
184
189
  * @param {string} cwd - Current working directory
185
190
  * @param {boolean} silent - Suppress console output (for testing)
186
- * @returns {{valid: boolean, errors: string[], warnings: string[]}} Validation result
191
+ * @returns {Promise<{valid: boolean, errors: string[], warnings: string[]}>} Validation result
187
192
  */
188
- export function validate(cwd = process.cwd(), silent = false) {
193
+ export async function validate(cwd = process.cwd(), silent = false) {
189
194
  const log = silent ? () => {} : console.log.bind(console);
190
195
  const error = silent ? () => {} : console.error.bind(console);
191
196
 
192
197
  const errors = [];
193
198
  const warnings = [];
194
199
 
200
+ // Load project config
201
+ const config = await loadConfig(cwd);
202
+ const standardPath = config.quality.standardPath;
203
+
195
204
  log('');
196
205
  log('Validating Quality Standard...');
197
206
  log('='.repeat(50));
198
207
 
199
208
  // 1. Check file exists
200
- const fileInfo = findQualityStandard(cwd);
209
+ const fileInfo = findQualityStandard(cwd, standardPath);
201
210
 
202
211
  if (!fileInfo) {
203
- errors.push('Missing required file: docs/quality/001-quality-standard.md');
212
+ errors.push(`Missing required file: ${standardPath}`);
204
213
  error('');
205
- error('ERROR: Missing required file: docs/quality/001-quality-standard.md');
214
+ error(`ERROR: Missing required file: ${standardPath}`);
206
215
  error('');
207
216
  error('Run "npx kabran-setup" to create it, or create manually.');
208
217
  log('='.repeat(50));
@@ -210,7 +219,7 @@ export function validate(cwd = process.cwd(), silent = false) {
210
219
  }
211
220
 
212
221
  log('');
213
- log('File: docs/quality/001-quality-standard.md');
222
+ log(`File: ${standardPath}`);
214
223
  log(' Status: Found');
215
224
 
216
225
  // 2. Read and parse content
@@ -317,10 +326,12 @@ export function validate(cwd = process.cwd(), silent = false) {
317
326
  /**
318
327
  * Get quality standard validation result in ci-result.json format
319
328
  * @param {string} [cwd] - Directory to validate (defaults to process.cwd())
320
- * @returns {Object} Check result for ci-result.json
329
+ * @returns {Promise<Object>} Check result for ci-result.json
321
330
  */
322
- export function getQualityStandardCheckResult(cwd = process.cwd()) {
323
- const fileInfo = findQualityStandard(cwd);
331
+ export async function getQualityStandardCheckResult(cwd = process.cwd()) {
332
+ const config = await loadConfig(cwd);
333
+ const standardPath = config.quality.standardPath;
334
+ const fileInfo = findQualityStandard(cwd, standardPath);
324
335
 
325
336
  if (!fileInfo) {
326
337
  return {
@@ -372,11 +383,11 @@ if (isMainModule) {
372
383
  const cwd = args.find(a => !a.startsWith('--')) || process.cwd();
373
384
 
374
385
  if (jsonOutput) {
375
- const result = getQualityStandardCheckResult(cwd);
386
+ const result = await getQualityStandardCheckResult(cwd);
376
387
  console.log(JSON.stringify(result, null, 2));
377
388
  process.exit(result.status === 'fail' ? 1 : 0);
378
389
  } else {
379
- const result = validate(cwd);
390
+ const result = await validate(cwd);
380
391
  process.exit(result.valid ? 0 : 1);
381
392
  }
382
393
  } catch (err) {
@@ -16,21 +16,27 @@
16
16
 
17
17
  import fs from 'node:fs';
18
18
  import path from 'node:path';
19
+ import {loadConfig, DEFAULTS} from '../core/config-loader.mjs';
19
20
 
20
- // Required sections (blocking if missing)
21
+ /**
22
+ * Build section patterns from config array.
23
+ * @param {string[]} sections - Section names
24
+ * @returns {Array<{pattern: RegExp, name: string}>}
25
+ */
26
+ function buildSectionPatterns(sections) {
27
+ return sections.map(name => ({
28
+ pattern: new RegExp(`^##\\s+${name}`, 'mi'),
29
+ name,
30
+ }));
31
+ }
32
+
33
+ // Default sections for backwards compatibility (used when importing directly)
21
34
  export const REQUIRED_SECTIONS = [
22
35
  {pattern: /^#\s+.+/m, name: 'Project Title (# Heading)'},
23
- {pattern: /^##\s+Installation/mi, name: 'Installation'},
24
- {pattern: /^##\s+Usage/mi, name: 'Usage'},
25
- {pattern: /^##\s+License/mi, name: 'License'},
36
+ ...buildSectionPatterns(DEFAULTS.readme.required),
26
37
  ];
27
38
 
28
- // Recommended sections (warnings only)
29
- export const RECOMMENDED_SECTIONS = [
30
- {pattern: /^##\s+Development/mi, name: 'Development'},
31
- {pattern: /^##\s+Contributing/mi, name: 'Contributing'},
32
- {pattern: /^##\s+Testing/mi, name: 'Testing'},
33
- ];
39
+ export const RECOMMENDED_SECTIONS = buildSectionPatterns(DEFAULTS.readme.recommended);
34
40
 
35
41
  /**
36
42
  * Find README.md in directory
@@ -64,9 +70,9 @@ export function checkSection(content, section) {
64
70
  * Main validation function
65
71
  * @param {string} [cwd] - Directory to validate (defaults to process.cwd())
66
72
  * @param {boolean} [silent] - Suppress console output
67
- * @returns {{valid: boolean, errors: string[], warnings: string[]}}
73
+ * @returns {Promise<{valid: boolean, errors: string[], warnings: string[]}>}
68
74
  */
69
- export function validateReadme(cwd = process.cwd(), silent = false) {
75
+ export async function validateReadme(cwd = process.cwd(), silent = false) {
70
76
  const log = silent ? () => {} : console.log.bind(console);
71
77
  const error = silent ? () => {} : console.error.bind(console);
72
78
 
@@ -75,6 +81,16 @@ export function validateReadme(cwd = process.cwd(), silent = false) {
75
81
  const errors = [];
76
82
  const warnings = [];
77
83
 
84
+ // Load project config
85
+ const config = await loadConfig(cwd);
86
+
87
+ // Build section patterns from config
88
+ const requiredSections = [
89
+ {pattern: /^#\s+.+/m, name: 'Project Title (# Heading)'},
90
+ ...buildSectionPatterns(config.readme.required),
91
+ ];
92
+ const recommendedSections = buildSectionPatterns(config.readme.recommended);
93
+
78
94
  // Check if README exists
79
95
  const readme = findReadme(cwd);
80
96
  if (!readme) {
@@ -90,7 +106,7 @@ export function validateReadme(cwd = process.cwd(), silent = false) {
90
106
 
91
107
  // Check required sections
92
108
  log('Checking required sections:');
93
- for (const section of REQUIRED_SECTIONS) {
109
+ for (const section of requiredSections) {
94
110
  const exists = checkSection(content, section);
95
111
  if (exists) {
96
112
  log(` OK ${section.name}`);
@@ -102,7 +118,7 @@ export function validateReadme(cwd = process.cwd(), silent = false) {
102
118
 
103
119
  // Check recommended sections
104
120
  log('\nChecking recommended sections:');
105
- for (const section of RECOMMENDED_SECTIONS) {
121
+ for (const section of recommendedSections) {
106
122
  const exists = checkSection(content, section);
107
123
  if (exists) {
108
124
  log(` OK ${section.name}`);
@@ -134,10 +150,10 @@ export function validateReadme(cwd = process.cwd(), silent = false) {
134
150
  /**
135
151
  * Get README validation result in ci-result.json format
136
152
  * @param {string} [cwd] - Directory to validate (defaults to process.cwd())
137
- * @returns {Object} Check result for ci-result.json
153
+ * @returns {Promise<Object>} Check result for ci-result.json
138
154
  */
139
- export function getReadmeCheckResult(cwd = process.cwd()) {
140
- const result = validateReadme(cwd, true);
155
+ export async function getReadmeCheckResult(cwd = process.cwd()) {
156
+ const result = await validateReadme(cwd, true);
141
157
 
142
158
  // Determine status
143
159
  let status = 'pass';
@@ -169,11 +185,11 @@ if (isMainModule) {
169
185
  const cwd = args.find(a => !a.startsWith('--')) || process.cwd();
170
186
 
171
187
  if (jsonOutput) {
172
- const result = getReadmeCheckResult(cwd);
188
+ const result = await getReadmeCheckResult(cwd);
173
189
  console.log(JSON.stringify(result, null, 2));
174
190
  process.exit(result.status === 'fail' ? 1 : 0);
175
191
  } else {
176
- const result = validateReadme(cwd);
192
+ const result = await validateReadme(cwd);
177
193
  process.exit(result.valid ? 0 : 1);
178
194
  }
179
195
  } catch (err) {
@@ -427,6 +427,7 @@ export function setupConfigs(projectDir, templatesDir, options) {
427
427
  // Other configs (same for all types)
428
428
  const otherConfigs = [
429
429
  {name: 'prettier.config.mjs', src: 'prettier.config.mjs'},
430
+ {name: '.prettierignore', src: '.prettierignore'},
430
431
  {name: 'commitlint.config.mjs', src: 'commitlint.config.mjs'},
431
432
  {name: 'lint-staged.config.mjs', src: 'lint-staged.config.mjs'},
432
433
  ];
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bash
2
2
  # ==============================================================================
3
3
  # Kabran Traceability Coverage Report
4
- # Part of @kabran-owner/kabran-config
4
+ # Part of @kabran-tecnologia/kabran-config
5
5
  # Implements PROP-006: JSDoc Traceability Tags
6
6
  #
7
7
  # Generates a report of spec coverage in the codebase.