@kabran-tecnologia/kabran-config 1.6.0 → 1.7.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.
- package/README.md +283 -0
- package/package.json +63 -8
- package/src/schemas/ci-result.v2.schema.json +125 -0
- package/src/scripts/ci/ci-runner.sh +85 -0
- package/src/scripts/ci-result-history.mjs +245 -0
- package/src/scripts/ci-result-trends.mjs +296 -0
- package/src/scripts/ci-result-utils.mjs +104 -0
- package/src/scripts/generate-ci-result.mjs +79 -11
- package/src/scripts/pr-quality-comment.mjs +36 -0
- package/src/scripts/setup.mjs +91 -4
- package/src/telemetry/config/defaults.mjs +421 -0
- package/src/telemetry/config/index.mjs +132 -0
- package/src/telemetry/edge/index.mjs +446 -0
- package/src/telemetry/frontend/index.mjs +366 -0
- package/src/telemetry/logger/index.mjs +236 -0
- package/src/telemetry/node/index.mjs +386 -0
- package/src/telemetry/shared/helpers.mjs +133 -0
- package/src/telemetry/shared/index.mjs +15 -0
- package/src/telemetry/shared/types.d.ts +123 -0
- package/templates/telemetry/.env.telemetry.example +118 -0
|
@@ -34,8 +34,14 @@ import {
|
|
|
34
34
|
calculateExecutionStats,
|
|
35
35
|
countIssues,
|
|
36
36
|
extractComponents,
|
|
37
|
+
getTraceId,
|
|
38
|
+
buildTelemetryExtension,
|
|
37
39
|
} from './ci-result-utils.mjs'
|
|
38
40
|
|
|
41
|
+
// History and trends imports
|
|
42
|
+
import { loadHistory, addToHistory, saveHistory } from './ci-result-history.mjs'
|
|
43
|
+
import { calculateTrends } from './ci-result-trends.mjs'
|
|
44
|
+
|
|
39
45
|
// Validator imports
|
|
40
46
|
import { getLicenseCheckResult } from './license-check.mjs'
|
|
41
47
|
import { getReadmeCheckResult } from './readme-validator.mjs'
|
|
@@ -132,6 +138,7 @@ async function runValidators(projectRoot, options = {}) {
|
|
|
132
138
|
* @param {Object} input.project - Project information
|
|
133
139
|
* @param {Object} input.metadata - Additional metadata
|
|
134
140
|
* @param {Object} input.validators - Validator results (from runValidators)
|
|
141
|
+
* @param {Object} input.trends - Pre-calculated trends (optional)
|
|
135
142
|
* @returns {Object} CI result object
|
|
136
143
|
*/
|
|
137
144
|
export function generateCiResult(input) {
|
|
@@ -143,6 +150,7 @@ export function generateCiResult(input) {
|
|
|
143
150
|
metadata = {},
|
|
144
151
|
issues = [],
|
|
145
152
|
validators = {},
|
|
153
|
+
trends = null,
|
|
146
154
|
} = input
|
|
147
155
|
|
|
148
156
|
const now = new Date().toISOString()
|
|
@@ -169,19 +177,36 @@ export function generateCiResult(input) {
|
|
|
169
177
|
// Determine exit code
|
|
170
178
|
const exitCode = executionStats.steps_failed > 0 ? 1 : 0
|
|
171
179
|
|
|
180
|
+
// Get trace ID if available
|
|
181
|
+
const traceId = getTraceId()
|
|
182
|
+
|
|
183
|
+
// Build meta object
|
|
184
|
+
const meta = {
|
|
185
|
+
generated_at: now,
|
|
186
|
+
generator: getGeneratorVersion(),
|
|
187
|
+
run_id: generateRunId(),
|
|
188
|
+
trigger: detectTrigger(),
|
|
189
|
+
branch: getGitBranch(),
|
|
190
|
+
commit: getGitCommit(),
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Add trace_id if available
|
|
194
|
+
if (traceId) {
|
|
195
|
+
meta.trace_id = traceId
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Build extensions with telemetry if trace_id exists
|
|
199
|
+
const extensions = { ...(metadata.extensions || {}) }
|
|
200
|
+
if (traceId) {
|
|
201
|
+
extensions.telemetry = buildTelemetryExtension(traceId)
|
|
202
|
+
}
|
|
203
|
+
|
|
172
204
|
// Build result object
|
|
173
205
|
const result = {
|
|
174
206
|
$schema: 'https://kabran.dev/schemas/ci-result.v2.json',
|
|
175
207
|
version: '1.0.0',
|
|
176
208
|
|
|
177
|
-
meta
|
|
178
|
-
generated_at: now,
|
|
179
|
-
generator: getGeneratorVersion(),
|
|
180
|
-
run_id: generateRunId(),
|
|
181
|
-
trigger: detectTrigger(),
|
|
182
|
-
branch: getGitBranch(),
|
|
183
|
-
commit: getGitCommit(),
|
|
184
|
-
},
|
|
209
|
+
meta,
|
|
185
210
|
|
|
186
211
|
project: {
|
|
187
212
|
name: project.name || 'unknown',
|
|
@@ -216,7 +241,12 @@ export function generateCiResult(input) {
|
|
|
216
241
|
checks,
|
|
217
242
|
issues,
|
|
218
243
|
errors,
|
|
219
|
-
extensions
|
|
244
|
+
extensions,
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Add trends if provided
|
|
248
|
+
if (trends) {
|
|
249
|
+
result.trends = trends
|
|
220
250
|
}
|
|
221
251
|
|
|
222
252
|
return result
|
|
@@ -236,6 +266,10 @@ function parseArgs(args) {
|
|
|
236
266
|
skipReadme: false,
|
|
237
267
|
skipEnv: false,
|
|
238
268
|
skipQualityStandard: false,
|
|
269
|
+
trackHistory: false,
|
|
270
|
+
historyFile: null,
|
|
271
|
+
maxHistoryEntries: 30,
|
|
272
|
+
calculateTrends: false,
|
|
239
273
|
}
|
|
240
274
|
|
|
241
275
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -259,6 +293,16 @@ function parseArgs(args) {
|
|
|
259
293
|
options.skipEnv = true
|
|
260
294
|
} else if (arg === '--skip-quality-standard') {
|
|
261
295
|
options.skipQualityStandard = true
|
|
296
|
+
} else if (arg === '--track-history') {
|
|
297
|
+
options.trackHistory = true
|
|
298
|
+
} else if (arg === '--history-file') {
|
|
299
|
+
options.historyFile = args[++i]
|
|
300
|
+
options.trackHistory = true
|
|
301
|
+
} else if (arg === '--max-history') {
|
|
302
|
+
options.maxHistoryEntries = parseInt(args[++i], 10) || 30
|
|
303
|
+
} else if (arg === '--calculate-trends') {
|
|
304
|
+
options.calculateTrends = true
|
|
305
|
+
options.trackHistory = true
|
|
262
306
|
} else if (arg === '--help' || arg === '-h') {
|
|
263
307
|
console.log(`
|
|
264
308
|
Usage: generate-ci-result.mjs [options]
|
|
@@ -273,6 +317,10 @@ Options:
|
|
|
273
317
|
--skip-readme Skip README check when running validators
|
|
274
318
|
--skip-env Skip env check when running validators
|
|
275
319
|
--skip-quality-standard Skip quality-standard check when running validators
|
|
320
|
+
--track-history Track result in history file (max 30 entries)
|
|
321
|
+
--history-file <file> Custom history file path (default: docs/quality/ci-result-history.json)
|
|
322
|
+
--max-history <n> Maximum history entries to keep (default: 30)
|
|
323
|
+
--calculate-trends Calculate and include trends from history
|
|
276
324
|
-h, --help Show this help message
|
|
277
325
|
|
|
278
326
|
Input format:
|
|
@@ -362,15 +410,35 @@ async function main() {
|
|
|
362
410
|
})
|
|
363
411
|
}
|
|
364
412
|
|
|
413
|
+
// Determine paths
|
|
414
|
+
const outputPath = options.output || resolve(options.projectRoot, 'docs/quality/ci-result.json')
|
|
415
|
+
const historyPath = options.historyFile || resolve(options.projectRoot, 'docs/quality/ci-result-history.json')
|
|
416
|
+
|
|
417
|
+
// Load history and calculate trends if requested
|
|
418
|
+
let history = null
|
|
419
|
+
if (options.trackHistory || options.calculateTrends) {
|
|
420
|
+
history = loadHistory(historyPath)
|
|
421
|
+
|
|
422
|
+
// Calculate trends if requested
|
|
423
|
+
if (options.calculateTrends && history.entries.length > 0) {
|
|
424
|
+
input.trends = calculateTrends(history.entries)
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
365
428
|
// Generate result
|
|
366
429
|
const result = generateCiResult(input)
|
|
367
430
|
|
|
431
|
+
// Update history with new result
|
|
432
|
+
if (options.trackHistory && history) {
|
|
433
|
+
addToHistory(result, history)
|
|
434
|
+
saveHistory(history, historyPath, options.maxHistoryEntries)
|
|
435
|
+
console.log(`History updated: ${historyPath} (${history.entries.length} entries)`)
|
|
436
|
+
}
|
|
437
|
+
|
|
368
438
|
// Output
|
|
369
439
|
if (options.stdout) {
|
|
370
440
|
console.log(JSON.stringify(result, null, 2))
|
|
371
441
|
} else {
|
|
372
|
-
const outputPath = options.output || resolve(options.projectRoot, 'docs/quality/ci-result.json')
|
|
373
|
-
|
|
374
442
|
// Ensure directory exists
|
|
375
443
|
mkdirSync(dirname(outputPath), { recursive: true })
|
|
376
444
|
|
|
@@ -142,12 +142,48 @@ export function generateComment(comparison, current, baseline) {
|
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
// Trends section (if available)
|
|
146
|
+
if (current.trends && current.trends.data?.total_runs > 1) {
|
|
147
|
+
lines.push(`### 📈 Trends`)
|
|
148
|
+
lines.push('')
|
|
149
|
+
lines.push(`| Metric | Direction | Change (7d) | Avg (30d) |`)
|
|
150
|
+
lines.push(`|--------|-----------|-------------|-----------|`)
|
|
151
|
+
|
|
152
|
+
const scoreTrend = current.trends.score
|
|
153
|
+
if (scoreTrend) {
|
|
154
|
+
const dirEmoji = scoreTrend.direction === 'improving' ? '📈' : scoreTrend.direction === 'degrading' ? '📉' : '➡️'
|
|
155
|
+
lines.push(`| Score | ${dirEmoji} ${scoreTrend.direction} | ${scoreTrend.change_7d !== null ? formatDiff(scoreTrend.change_7d) : 'N/A'} | ${scoreTrend.avg_30d || 'N/A'} |`)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const perfTrend = current.trends.performance
|
|
159
|
+
if (perfTrend && perfTrend.avg_30d) {
|
|
160
|
+
const dirEmoji = perfTrend.direction === 'improving' ? '📈' : perfTrend.direction === 'degrading' ? '📉' : '➡️'
|
|
161
|
+
const change7d = perfTrend.change_7d !== null ? `${(perfTrend.change_7d / 1000).toFixed(1)}s` : 'N/A'
|
|
162
|
+
lines.push(`| Duration | ${dirEmoji} ${perfTrend.direction} | ${change7d} | ${(perfTrend.avg_30d / 1000).toFixed(1)}s |`)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
lines.push('')
|
|
166
|
+
lines.push(`<sub>Based on ${current.trends.data.total_runs} runs</sub>`)
|
|
167
|
+
lines.push('')
|
|
168
|
+
}
|
|
169
|
+
|
|
145
170
|
// Timing info
|
|
146
171
|
lines.push(`---`)
|
|
147
172
|
lines.push(`<sub>`)
|
|
148
173
|
lines.push(`⏱️ Duration: ${current.timing?.total_human || 'N/A'} | `)
|
|
149
174
|
lines.push(`🔀 Branch: ${current.meta?.branch || 'N/A'} | `)
|
|
150
175
|
lines.push(`📝 Commit: ${current.meta?.commit || 'N/A'}`)
|
|
176
|
+
|
|
177
|
+
// Add trace link if available
|
|
178
|
+
if (current.meta?.trace_id) {
|
|
179
|
+
const traceUrl = current.extensions?.telemetry?.trace_url
|
|
180
|
+
if (traceUrl) {
|
|
181
|
+
lines.push(` | 🔍 [Trace](${traceUrl})`)
|
|
182
|
+
} else {
|
|
183
|
+
lines.push(` | 🔍 Trace: ${current.meta.trace_id}`)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
151
187
|
lines.push(`</sub>`)
|
|
152
188
|
|
|
153
189
|
return lines.join('\n')
|
package/src/scripts/setup.mjs
CHANGED
|
@@ -79,6 +79,7 @@ export function parseArgs(args) {
|
|
|
79
79
|
skipQualityStandard: false,
|
|
80
80
|
syncWorkflows: false,
|
|
81
81
|
syncHusky: false,
|
|
82
|
+
telemetryEnv: false,
|
|
82
83
|
force: false,
|
|
83
84
|
dryRun: false,
|
|
84
85
|
help: false,
|
|
@@ -97,6 +98,8 @@ export function parseArgs(args) {
|
|
|
97
98
|
options.syncWorkflows = true;
|
|
98
99
|
} else if (arg === '--sync-husky') {
|
|
99
100
|
options.syncHusky = true;
|
|
101
|
+
} else if (arg === '--telemetry-env') {
|
|
102
|
+
options.telemetryEnv = true;
|
|
100
103
|
} else if (arg === '--force') {
|
|
101
104
|
options.force = true;
|
|
102
105
|
} else if (arg === '--dry-run') {
|
|
@@ -132,6 +135,7 @@ ${colors.yellow}OPTIONS:${colors.reset}
|
|
|
132
135
|
--skip-quality-standard Don't create quality-standard.md
|
|
133
136
|
--sync-workflows Overwrite existing workflow files
|
|
134
137
|
--sync-husky Overwrite existing husky hooks
|
|
138
|
+
--telemetry-env Generate .env.example with telemetry variables
|
|
135
139
|
--force Overwrite all existing files
|
|
136
140
|
--dry-run Preview changes without modifying files
|
|
137
141
|
--help, -h Show this help message
|
|
@@ -146,6 +150,9 @@ ${colors.yellow}EXAMPLES:${colors.reset}
|
|
|
146
150
|
# Update workflows only
|
|
147
151
|
npx kabran-setup --sync-workflows
|
|
148
152
|
|
|
153
|
+
# Generate telemetry .env.example
|
|
154
|
+
npx kabran-setup --telemetry-env
|
|
155
|
+
|
|
149
156
|
# Preview changes without modifying
|
|
150
157
|
npx kabran-setup --dry-run
|
|
151
158
|
|
|
@@ -547,6 +554,79 @@ export function setupQualityStandard(projectDir, templatesDir, options) {
|
|
|
547
554
|
return results;
|
|
548
555
|
}
|
|
549
556
|
|
|
557
|
+
/**
|
|
558
|
+
* Setup telemetry .env.example
|
|
559
|
+
* @param {string} projectDir - Project directory
|
|
560
|
+
* @param {string} templatesDir - Templates directory
|
|
561
|
+
* @param {object} options - Setup options
|
|
562
|
+
* @returns {object} Results
|
|
563
|
+
*/
|
|
564
|
+
export function setupTelemetryEnv(projectDir, templatesDir, options) {
|
|
565
|
+
const {force = false, dryRun = false} = options;
|
|
566
|
+
|
|
567
|
+
const results = {
|
|
568
|
+
created: 0,
|
|
569
|
+
overwritten: 0,
|
|
570
|
+
skipped: 0,
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
logInfo('Setting up telemetry .env.example...');
|
|
574
|
+
|
|
575
|
+
const src = join(templatesDir, 'telemetry', '.env.telemetry.example');
|
|
576
|
+
const dest = join(projectDir, '.env.example');
|
|
577
|
+
|
|
578
|
+
// Check if source template exists
|
|
579
|
+
if (!existsSync(src)) {
|
|
580
|
+
logWarn('Template not found: templates/telemetry/.env.telemetry.example');
|
|
581
|
+
return results;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Check if destination exists
|
|
585
|
+
const destExists = existsSync(dest);
|
|
586
|
+
|
|
587
|
+
if (destExists && !force) {
|
|
588
|
+
// If .env.example exists, append telemetry section if not present
|
|
589
|
+
const existingContent = readFileSync(dest, 'utf-8');
|
|
590
|
+
|
|
591
|
+
if (existingContent.includes('Kabran Telemetry Configuration')) {
|
|
592
|
+
if (dryRun) {
|
|
593
|
+
logDry('Would skip .env.example (telemetry section already present)');
|
|
594
|
+
} else {
|
|
595
|
+
logSkip('.env.example (telemetry section already present)');
|
|
596
|
+
}
|
|
597
|
+
results.skipped = 1;
|
|
598
|
+
return results;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Append telemetry section
|
|
602
|
+
const telemetryContent = readFileSync(src, 'utf-8');
|
|
603
|
+
|
|
604
|
+
if (dryRun) {
|
|
605
|
+
logDry('Would append telemetry section to .env.example');
|
|
606
|
+
results.overwritten = 1;
|
|
607
|
+
return results;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const newContent = existingContent.trimEnd() + '\n\n' + telemetryContent;
|
|
611
|
+
writeFileSync(dest, newContent, 'utf-8');
|
|
612
|
+
logSuccess('Appended telemetry section to: .env.example');
|
|
613
|
+
results.overwritten = 1;
|
|
614
|
+
return results;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const status = copyFile(src, dest, {overwrite: force, dryRun});
|
|
618
|
+
|
|
619
|
+
if (status === 'created' || status === 'would_create') {
|
|
620
|
+
results.created = 1;
|
|
621
|
+
} else if (status === 'overwritten' || status === 'would_overwrite') {
|
|
622
|
+
results.overwritten = 1;
|
|
623
|
+
} else {
|
|
624
|
+
results.skipped = 1;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
return results;
|
|
628
|
+
}
|
|
629
|
+
|
|
550
630
|
/**
|
|
551
631
|
* Run setup
|
|
552
632
|
* @param {string} projectDir - Project directory
|
|
@@ -561,6 +641,7 @@ export function runSetup(projectDir, options) {
|
|
|
561
641
|
husky: {created: 0, overwritten: 0, skipped: 0},
|
|
562
642
|
configs: {created: 0, overwritten: 0, skipped: 0},
|
|
563
643
|
qualityStandard: {created: 0, overwritten: 0, skipped: 0},
|
|
644
|
+
telemetryEnv: {created: 0, overwritten: 0, skipped: 0},
|
|
564
645
|
};
|
|
565
646
|
|
|
566
647
|
console.log('');
|
|
@@ -592,11 +673,17 @@ export function runSetup(projectDir, options) {
|
|
|
592
673
|
}
|
|
593
674
|
|
|
594
675
|
// Setup quality-standard.md (unless skipped or in sync mode)
|
|
595
|
-
if (!options.skipQualityStandard && !isSyncMode) {
|
|
676
|
+
if (!options.skipQualityStandard && !isSyncMode && !options.telemetryEnv) {
|
|
596
677
|
summary.qualityStandard = setupQualityStandard(projectDir, templatesDir, options);
|
|
597
678
|
console.log('');
|
|
598
679
|
}
|
|
599
680
|
|
|
681
|
+
// Setup telemetry .env.example (only when explicitly requested)
|
|
682
|
+
if (options.telemetryEnv) {
|
|
683
|
+
summary.telemetryEnv = setupTelemetryEnv(projectDir, templatesDir, options);
|
|
684
|
+
console.log('');
|
|
685
|
+
}
|
|
686
|
+
|
|
600
687
|
return summary;
|
|
601
688
|
}
|
|
602
689
|
|
|
@@ -609,10 +696,10 @@ export function printSummary(summary) {
|
|
|
609
696
|
console.log(`${colors.cyan}=== Setup Summary ===${colors.reset}`);
|
|
610
697
|
|
|
611
698
|
const total = {
|
|
612
|
-
created: summary.workflows.created + summary.husky.created + summary.configs.created + summary.qualityStandard.created,
|
|
699
|
+
created: summary.workflows.created + summary.husky.created + summary.configs.created + summary.qualityStandard.created + summary.telemetryEnv.created,
|
|
613
700
|
overwritten:
|
|
614
|
-
summary.workflows.overwritten + summary.husky.overwritten + summary.configs.overwritten + summary.qualityStandard.overwritten,
|
|
615
|
-
skipped: summary.workflows.skipped + summary.husky.skipped + summary.configs.skipped + summary.qualityStandard.skipped,
|
|
701
|
+
summary.workflows.overwritten + summary.husky.overwritten + summary.configs.overwritten + summary.qualityStandard.overwritten + summary.telemetryEnv.overwritten,
|
|
702
|
+
skipped: summary.workflows.skipped + summary.husky.skipped + summary.configs.skipped + summary.qualityStandard.skipped + summary.telemetryEnv.skipped,
|
|
616
703
|
};
|
|
617
704
|
|
|
618
705
|
if (total.created > 0) {
|