@stati/core 1.16.3 → 1.17.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,64 @@
1
+ /**
2
+ * Metrics file writing utilities.
3
+ * Handles graceful degradation - never blocks builds on write failures.
4
+ */
5
+ import type { BuildMetrics } from '../types.js';
6
+ /**
7
+ * Default metrics output directory (relative to cache dir).
8
+ */
9
+ export declare const DEFAULT_METRICS_DIR = "metrics";
10
+ /**
11
+ * Generate a timestamped filename for metrics output.
12
+ *
13
+ * @param command - The command that was run (build, dev)
14
+ * @param timestamp - ISO timestamp
15
+ * @returns Filename like "build-2024-01-15T10-30-00.json"
16
+ */
17
+ export declare function generateMetricsFilename(command: string, timestamp: string): string;
18
+ /**
19
+ * Options for writing metrics.
20
+ */
21
+ export interface WriteMetricsOptions {
22
+ /** Base cache directory (e.g., .stati) */
23
+ cacheDir: string;
24
+ /** Custom output path (overrides default) */
25
+ outputPath?: string | undefined;
26
+ /** Format: json (default) or ndjson */
27
+ format?: 'json' | 'ndjson' | undefined;
28
+ }
29
+ /**
30
+ * Result of metrics write operation.
31
+ */
32
+ export interface WriteMetricsResult {
33
+ success: boolean;
34
+ path?: string;
35
+ error?: string;
36
+ }
37
+ /**
38
+ * Write build metrics to a JSON file.
39
+ * Degrades gracefully on failure - logs warning but doesn't throw.
40
+ *
41
+ * @param metrics - The metrics to write
42
+ * @param options - Write options
43
+ * @returns Result indicating success/failure and path
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * const result = await writeMetrics(metrics, {
48
+ * cacheDir: '.stati',
49
+ * });
50
+ *
51
+ * if (result.success) {
52
+ * console.log(`Metrics written to ${result.path}`);
53
+ * }
54
+ * ```
55
+ */
56
+ export declare function writeMetrics(metrics: BuildMetrics, options: WriteMetricsOptions): Promise<WriteMetricsResult>;
57
+ /**
58
+ * Format metrics for CLI summary output.
59
+ *
60
+ * @param metrics - Build metrics to format
61
+ * @returns Array of formatted lines for CLI output
62
+ */
63
+ export declare function formatMetricsSummary(metrics: BuildMetrics): string[];
64
+ //# sourceMappingURL=writer.utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writer.utils.d.ts","sourceRoot":"","sources":["../../../src/metrics/utils/writer.utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;GAEG;AACH,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAuB7C;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAIlF;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;CACxC;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,kBAAkB,CAAC,CA2C7B;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,EAAE,CA6CpE"}
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Metrics file writing utilities.
3
+ * Handles graceful degradation - never blocks builds on write failures.
4
+ */
5
+ import { writeFile, mkdir } from 'node:fs/promises';
6
+ import { join, dirname } from 'node:path';
7
+ /**
8
+ * Default metrics output directory (relative to cache dir).
9
+ */
10
+ export const DEFAULT_METRICS_DIR = 'metrics';
11
+ /**
12
+ * Display names for build phases.
13
+ * Maps raw phase keys (e.g., 'configLoadMs') to human-readable labels.
14
+ */
15
+ const PHASE_DISPLAY_NAMES = {
16
+ configLoadMs: 'Config Load',
17
+ contentDiscoveryMs: 'Content Discovery',
18
+ navigationBuildMs: 'Navigation Build',
19
+ cacheManifestLoadMs: 'Cache Manifest Load',
20
+ typescriptCompileMs: 'TypeScript Compile',
21
+ pageRenderingMs: 'Page Rendering',
22
+ assetCopyMs: 'Asset Copy',
23
+ cacheManifestSaveMs: 'Cache Manifest Save',
24
+ sitemapGenerationMs: 'Sitemap Generation',
25
+ rssGenerationMs: 'RSS Generation',
26
+ hookBeforeAllMs: 'Hook: Before All',
27
+ hookAfterAllMs: 'Hook: After All',
28
+ hookBeforeRenderTotalMs: 'Hook: Before Render (Total)',
29
+ hookAfterRenderTotalMs: 'Hook: After Render (Total)',
30
+ };
31
+ /**
32
+ * Generate a timestamped filename for metrics output.
33
+ *
34
+ * @param command - The command that was run (build, dev)
35
+ * @param timestamp - ISO timestamp
36
+ * @returns Filename like "build-2024-01-15T10-30-00.json"
37
+ */
38
+ export function generateMetricsFilename(command, timestamp) {
39
+ // Replace colons with dashes for Windows compatibility
40
+ const safeTimestamp = timestamp.replace(/:/g, '-').replace(/\.\d+Z$/, 'Z');
41
+ return `${command}-${safeTimestamp}.json`;
42
+ }
43
+ /**
44
+ * Write build metrics to a JSON file.
45
+ * Degrades gracefully on failure - logs warning but doesn't throw.
46
+ *
47
+ * @param metrics - The metrics to write
48
+ * @param options - Write options
49
+ * @returns Result indicating success/failure and path
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * const result = await writeMetrics(metrics, {
54
+ * cacheDir: '.stati',
55
+ * });
56
+ *
57
+ * if (result.success) {
58
+ * console.log(`Metrics written to ${result.path}`);
59
+ * }
60
+ * ```
61
+ */
62
+ export async function writeMetrics(metrics, options) {
63
+ try {
64
+ // Determine output path
65
+ let outputPath;
66
+ if (options.outputPath) {
67
+ outputPath = options.outputPath;
68
+ }
69
+ else {
70
+ const metricsDir = join(options.cacheDir, DEFAULT_METRICS_DIR);
71
+ const filename = generateMetricsFilename(metrics.meta.command, metrics.meta.timestamp);
72
+ outputPath = join(metricsDir, filename);
73
+ }
74
+ // Ensure directory exists
75
+ await mkdir(dirname(outputPath), { recursive: true });
76
+ // Format content
77
+ const format = options.format ?? 'json';
78
+ let content;
79
+ if (format === 'ndjson') {
80
+ // NDJSON: one JSON object per line, no pretty printing
81
+ content = JSON.stringify(metrics) + '\n';
82
+ }
83
+ else {
84
+ // JSON: pretty printed for readability
85
+ content = JSON.stringify(metrics, null, 2) + '\n';
86
+ }
87
+ // Write file
88
+ await writeFile(outputPath, content, 'utf-8');
89
+ return {
90
+ success: true,
91
+ path: outputPath,
92
+ };
93
+ }
94
+ catch (error) {
95
+ // Graceful degradation - don't throw, just report failure
96
+ const errorMessage = error instanceof Error ? error.message : String(error);
97
+ return {
98
+ success: false,
99
+ error: `Failed to write metrics: ${errorMessage}`,
100
+ };
101
+ }
102
+ }
103
+ /**
104
+ * Format metrics for CLI summary output.
105
+ *
106
+ * @param metrics - Build metrics to format
107
+ * @returns Array of formatted lines for CLI output
108
+ */
109
+ export function formatMetricsSummary(metrics) {
110
+ const lines = [];
111
+ // Header
112
+ lines.push('');
113
+ lines.push('Build Metrics Summary');
114
+ lines.push('─'.repeat(40));
115
+ // Total duration
116
+ const totalSeconds = (metrics.totals.durationMs / 1000).toFixed(2);
117
+ lines.push(`Total build time: ${totalSeconds}s`);
118
+ // Page stats
119
+ const { totalPages, renderedPages, cachedPages } = metrics.counts;
120
+ lines.push(`Pages: ${totalPages} total, ${renderedPages} rendered, ${cachedPages} cached`);
121
+ // Cache hit rate
122
+ const hitRate = (metrics.isg.cacheHitRate * 100).toFixed(1);
123
+ lines.push(`Cache hit rate: ${hitRate}%`);
124
+ // Memory
125
+ const peakMB = (metrics.totals.peakRssBytes / 1024 / 1024).toFixed(1);
126
+ lines.push(`Peak memory: ${peakMB} MB`);
127
+ // Top phases (sorted by duration, top 3)
128
+ const phases = Object.entries(metrics.phases)
129
+ .filter(([, duration]) => duration !== undefined && duration > 0)
130
+ .map(([name, duration]) => ({ name, duration: duration }))
131
+ .sort((a, b) => b.duration - a.duration)
132
+ .slice(0, 3);
133
+ if (phases.length > 0) {
134
+ lines.push('');
135
+ lines.push('Top phases:');
136
+ for (const phase of phases) {
137
+ // Use mapped display name if available, otherwise fall back to raw name
138
+ const phaseName = PHASE_DISPLAY_NAMES[phase.name] || phase.name;
139
+ const phaseMs = phase.duration.toFixed(0);
140
+ lines.push(` ${phaseName}: ${phaseMs}ms`);
141
+ }
142
+ }
143
+ lines.push('─'.repeat(40));
144
+ return lines;
145
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stati/core",
3
- "version": "1.16.3",
3
+ "version": "1.17.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",