@stati/core 1.16.2 → 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.
Files changed (37) hide show
  1. package/dist/core/build.d.ts +41 -12
  2. package/dist/core/build.d.ts.map +1 -1
  3. package/dist/core/build.js +92 -11
  4. package/dist/core/dev.js +3 -3
  5. package/dist/core/index.d.ts +1 -1
  6. package/dist/core/index.d.ts.map +1 -1
  7. package/dist/core/isg/ttl.js +1 -1
  8. package/dist/core/templates.d.ts +10 -1
  9. package/dist/core/templates.d.ts.map +1 -1
  10. package/dist/core/templates.js +11 -14
  11. package/dist/core/utils/glob-patterns.utils.d.ts.map +1 -1
  12. package/dist/core/utils/glob-patterns.utils.js +1 -6
  13. package/dist/index.d.ts +3 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1 -0
  16. package/dist/metrics/index.d.ts +38 -0
  17. package/dist/metrics/index.d.ts.map +1 -0
  18. package/dist/metrics/index.js +42 -0
  19. package/dist/metrics/noop.d.ts +12 -0
  20. package/dist/metrics/noop.d.ts.map +1 -0
  21. package/dist/metrics/noop.js +88 -0
  22. package/dist/metrics/recorder.d.ts +31 -0
  23. package/dist/metrics/recorder.d.ts.map +1 -0
  24. package/dist/metrics/recorder.js +176 -0
  25. package/dist/metrics/types.d.ts +236 -0
  26. package/dist/metrics/types.d.ts.map +1 -0
  27. package/dist/metrics/types.js +7 -0
  28. package/dist/metrics/utils/index.d.ts +9 -0
  29. package/dist/metrics/utils/index.d.ts.map +1 -0
  30. package/dist/metrics/utils/index.js +9 -0
  31. package/dist/metrics/utils/system.utils.d.ts +44 -0
  32. package/dist/metrics/utils/system.utils.d.ts.map +1 -0
  33. package/dist/metrics/utils/system.utils.js +95 -0
  34. package/dist/metrics/utils/writer.utils.d.ts +64 -0
  35. package/dist/metrics/utils/writer.utils.d.ts.map +1 -0
  36. package/dist/metrics/utils/writer.utils.js +145 -0
  37. package/package.json +1 -1
@@ -0,0 +1,95 @@
1
+ /**
2
+ * System information utilities for metrics collection.
3
+ * Uses only Node.js built-ins to avoid external dependencies.
4
+ */
5
+ import { execSync } from 'node:child_process';
6
+ import { cpus, platform, arch } from 'node:os';
7
+ /**
8
+ * CI environment detection environment variables.
9
+ */
10
+ const CI_ENV_VARS = [
11
+ 'CI',
12
+ 'GITHUB_ACTIONS',
13
+ 'GITLAB_CI',
14
+ 'CIRCLECI',
15
+ 'TRAVIS',
16
+ 'JENKINS_URL',
17
+ 'BUILDKITE',
18
+ 'AZURE_PIPELINES',
19
+ 'TF_BUILD',
20
+ ];
21
+ /**
22
+ * Detect if running in a CI environment.
23
+ */
24
+ export function isCI() {
25
+ return CI_ENV_VARS.some((envVar) => process.env[envVar] !== undefined);
26
+ }
27
+ /**
28
+ * Get the current Git commit SHA.
29
+ * Returns undefined if not in a git repository or git is not available.
30
+ */
31
+ export function getGitCommit() {
32
+ try {
33
+ const result = execSync('git rev-parse HEAD', {
34
+ encoding: 'utf-8',
35
+ stdio: ['pipe', 'pipe', 'pipe'],
36
+ timeout: 5000,
37
+ });
38
+ return result.trim() || undefined;
39
+ }
40
+ catch {
41
+ return undefined;
42
+ }
43
+ }
44
+ /**
45
+ * Get the current Git branch name.
46
+ * Returns undefined if not in a git repository or git is not available.
47
+ */
48
+ export function getGitBranch() {
49
+ try {
50
+ const result = execSync('git rev-parse --abbrev-ref HEAD', {
51
+ encoding: 'utf-8',
52
+ stdio: ['pipe', 'pipe', 'pipe'],
53
+ timeout: 5000,
54
+ });
55
+ return result.trim() || undefined;
56
+ }
57
+ catch {
58
+ return undefined;
59
+ }
60
+ }
61
+ /**
62
+ * Get the number of CPU cores.
63
+ */
64
+ export function getCpuCount() {
65
+ return cpus().length;
66
+ }
67
+ /**
68
+ * Get the current platform.
69
+ */
70
+ export function getPlatform() {
71
+ return platform();
72
+ }
73
+ /**
74
+ * Get the CPU architecture.
75
+ */
76
+ export function getArch() {
77
+ return arch();
78
+ }
79
+ /**
80
+ * Get Node.js version string (without the 'v' prefix).
81
+ */
82
+ export function getNodeVersion() {
83
+ return process.version.replace(/^v/, '');
84
+ }
85
+ /**
86
+ * Get current memory usage snapshot.
87
+ */
88
+ export function getMemoryUsage() {
89
+ const usage = process.memoryUsage();
90
+ return {
91
+ rss: usage.rss,
92
+ heapUsed: usage.heapUsed,
93
+ heapTotal: usage.heapTotal,
94
+ };
95
+ }
@@ -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.2",
3
+ "version": "1.17.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",