@stati/core 1.16.3 → 1.18.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/dist/core/build.d.ts +41 -12
- package/dist/core/build.d.ts.map +1 -1
- package/dist/core/build.js +95 -13
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/markdown.d.ts +19 -2
- package/dist/core/markdown.d.ts.map +1 -1
- package/dist/core/markdown.js +81 -2
- package/dist/core/templates.d.ts +11 -2
- package/dist/core/templates.d.ts.map +1 -1
- package/dist/core/templates.js +28 -11
- package/dist/core/utils/index.d.ts +1 -0
- package/dist/core/utils/index.d.ts.map +1 -1
- package/dist/core/utils/index.js +2 -0
- package/dist/core/utils/slugify.utils.d.ts +22 -0
- package/dist/core/utils/slugify.utils.d.ts.map +1 -0
- package/dist/core/utils/slugify.utils.js +108 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/metrics/index.d.ts +38 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +42 -0
- package/dist/metrics/noop.d.ts +12 -0
- package/dist/metrics/noop.d.ts.map +1 -0
- package/dist/metrics/noop.js +88 -0
- package/dist/metrics/recorder.d.ts +31 -0
- package/dist/metrics/recorder.d.ts.map +1 -0
- package/dist/metrics/recorder.js +176 -0
- package/dist/metrics/types.d.ts +236 -0
- package/dist/metrics/types.d.ts.map +1 -0
- package/dist/metrics/types.js +7 -0
- package/dist/metrics/utils/index.d.ts +9 -0
- package/dist/metrics/utils/index.d.ts.map +1 -0
- package/dist/metrics/utils/index.js +9 -0
- package/dist/metrics/utils/system.utils.d.ts +44 -0
- package/dist/metrics/utils/system.utils.d.ts.map +1 -0
- package/dist/metrics/utils/system.utils.js +95 -0
- package/dist/metrics/utils/writer.utils.d.ts +64 -0
- package/dist/metrics/utils/writer.utils.d.ts.map +1 -0
- package/dist/metrics/utils/writer.utils.js +145 -0
- package/dist/types/config.d.ts +2 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/content.d.ts +23 -4
- package/dist/types/content.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slugify utilities for generating URL-friendly anchor IDs.
|
|
3
|
+
* @module core/utils/slugify
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Converts a string to a URL-friendly slug.
|
|
7
|
+
* Used for generating heading anchor IDs.
|
|
8
|
+
*
|
|
9
|
+
* @param text - The text to slugify
|
|
10
|
+
* @returns A URL-friendly slug
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* slugify('Hello World') // 'hello-world'
|
|
15
|
+
* slugify('Getting Started!') // 'getting-started'
|
|
16
|
+
* slugify(' Multiple Spaces ') // 'multiple-spaces'
|
|
17
|
+
* slugify('Héllo Wörld') // 'hello-world'
|
|
18
|
+
* slugify('Cats & Dogs') // 'cats-and-dogs'
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare function slugify(text: string): string;
|
|
22
|
+
//# sourceMappingURL=slugify.utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slugify.utils.d.ts","sourceRoot":"","sources":["../../../src/core/utils/slugify.utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAyDH;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAqC5C"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slugify utilities for generating URL-friendly anchor IDs.
|
|
3
|
+
* @module core/utils/slugify
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Common character transliteration map for accented/special characters.
|
|
7
|
+
*/
|
|
8
|
+
const TRANSLITERATION_MAP = {
|
|
9
|
+
// Latin extended characters
|
|
10
|
+
à: 'a',
|
|
11
|
+
á: 'a',
|
|
12
|
+
â: 'a',
|
|
13
|
+
ã: 'a',
|
|
14
|
+
ä: 'a',
|
|
15
|
+
å: 'a',
|
|
16
|
+
æ: 'ae',
|
|
17
|
+
ç: 'c',
|
|
18
|
+
č: 'c',
|
|
19
|
+
ć: 'c',
|
|
20
|
+
ð: 'd',
|
|
21
|
+
đ: 'd',
|
|
22
|
+
è: 'e',
|
|
23
|
+
é: 'e',
|
|
24
|
+
ê: 'e',
|
|
25
|
+
ë: 'e',
|
|
26
|
+
ě: 'e',
|
|
27
|
+
ì: 'i',
|
|
28
|
+
í: 'i',
|
|
29
|
+
î: 'i',
|
|
30
|
+
ï: 'i',
|
|
31
|
+
ł: 'l',
|
|
32
|
+
ñ: 'n',
|
|
33
|
+
ń: 'n',
|
|
34
|
+
ò: 'o',
|
|
35
|
+
ó: 'o',
|
|
36
|
+
ô: 'o',
|
|
37
|
+
õ: 'o',
|
|
38
|
+
ö: 'o',
|
|
39
|
+
ø: 'o',
|
|
40
|
+
œ: 'oe',
|
|
41
|
+
ř: 'r',
|
|
42
|
+
š: 's',
|
|
43
|
+
ś: 's',
|
|
44
|
+
ß: 'ss',
|
|
45
|
+
ù: 'u',
|
|
46
|
+
ú: 'u',
|
|
47
|
+
û: 'u',
|
|
48
|
+
ü: 'u',
|
|
49
|
+
ů: 'u',
|
|
50
|
+
ű: 'u',
|
|
51
|
+
ý: 'y',
|
|
52
|
+
ÿ: 'y',
|
|
53
|
+
ž: 'z',
|
|
54
|
+
ź: 'z',
|
|
55
|
+
ż: 'z',
|
|
56
|
+
// Additional common characters
|
|
57
|
+
þ: 'th',
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Converts a string to a URL-friendly slug.
|
|
61
|
+
* Used for generating heading anchor IDs.
|
|
62
|
+
*
|
|
63
|
+
* @param text - The text to slugify
|
|
64
|
+
* @returns A URL-friendly slug
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* slugify('Hello World') // 'hello-world'
|
|
69
|
+
* slugify('Getting Started!') // 'getting-started'
|
|
70
|
+
* slugify(' Multiple Spaces ') // 'multiple-spaces'
|
|
71
|
+
* slugify('Héllo Wörld') // 'hello-world'
|
|
72
|
+
* slugify('Cats & Dogs') // 'cats-and-dogs'
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export function slugify(text) {
|
|
76
|
+
// Handle null, undefined, or non-string input defensively
|
|
77
|
+
if (text === null || text === undefined) {
|
|
78
|
+
return '';
|
|
79
|
+
}
|
|
80
|
+
// Coerce to string if needed
|
|
81
|
+
const input = String(text);
|
|
82
|
+
return (input
|
|
83
|
+
.toLowerCase()
|
|
84
|
+
.trim()
|
|
85
|
+
// Normalize unicode (NFD decomposes, then we handle diacritics)
|
|
86
|
+
.normalize('NFD')
|
|
87
|
+
// Apply transliteration map for known characters
|
|
88
|
+
// eslint-disable-next-line no-control-regex
|
|
89
|
+
.replace(/[^\u0000-\u007F]/g, (char) => {
|
|
90
|
+
// First check our transliteration map
|
|
91
|
+
if (TRANSLITERATION_MAP[char]) {
|
|
92
|
+
return TRANSLITERATION_MAP[char];
|
|
93
|
+
}
|
|
94
|
+
// Remove combining diacritical marks (accents) that remain after NFD normalization
|
|
95
|
+
if (/[\u0300-\u036f]/.test(char)) {
|
|
96
|
+
return '';
|
|
97
|
+
}
|
|
98
|
+
// Replace other non-ASCII characters (emojis, symbols, CJK, etc.) with space
|
|
99
|
+
// to avoid creating consecutive dashes
|
|
100
|
+
return ' ';
|
|
101
|
+
})
|
|
102
|
+
// Replace & with 'and' (handle case where it wasn't caught by transliteration)
|
|
103
|
+
.replace(/&/g, '-and-')
|
|
104
|
+
// Replace any sequence of whitespace or non-word characters with a single dash
|
|
105
|
+
.replace(/[\s\W-]+/g, '-')
|
|
106
|
+
// Remove leading and trailing dashes
|
|
107
|
+
.replace(/^-+|-+$/g, ''));
|
|
108
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -23,8 +23,10 @@ export type { StatiConfig, PageModel, FrontMatter, BuildContext, PageContext, Bu
|
|
|
23
23
|
export type { SEOMetadata, SEOConfig, SEOContext, SEOValidationResult, SEOTagType, RobotsConfig, OpenGraphConfig, OpenGraphImage, OpenGraphArticle, TwitterCardConfig, AuthorConfig, } from './types/index.js';
|
|
24
24
|
export type { SitemapConfig, SitemapEntry, SitemapGenerationResult, ChangeFrequency, } from './types/index.js';
|
|
25
25
|
export type { RSSConfig, RSSFeedConfig, RSSGenerationResult } from './types/index.js';
|
|
26
|
-
export type { BuildOptions, DevServerOptions, PreviewServerOptions, InvalidationResult, } from './core/index.js';
|
|
26
|
+
export type { BuildOptions, MetricsOptions, BuildResult, DevServerOptions, PreviewServerOptions, InvalidationResult, } from './core/index.js';
|
|
27
27
|
export { build, createDevServer, createPreviewServer, invalidate } from './core/index.js';
|
|
28
|
+
export type { BuildMetrics, MetricsMeta, MetricsFlags, MetricsTotals, MetricsPhases, MetricsCounts, MetricsISG, PageTiming, IncrementalMetrics, MetricRecorder, MetricRecorderOptions, PhaseName, CounterName, } from './metrics/index.js';
|
|
29
|
+
export { createMetricRecorder, noopMetricRecorder, writeMetrics, formatMetricsSummary, } from './metrics/index.js';
|
|
28
30
|
export type { AutoInjectOptions } from './seo/index.js';
|
|
29
31
|
export { generateSEOMetadata, generateSEO, generateOpenGraphTags, generateTwitterCardTags, generateSitemap, generateSitemapEntry, generateSitemapXml, generateSitemapIndexXml, generateRobotsTxt, generateRobotsTxtFromConfig, escapeHtml, generateRobotsContent, validateSEOMetadata, detectExistingSEOTags, normalizeUrlPath, resolveAbsoluteUrl, isValidUrl, autoInjectSEO, shouldAutoInject, } from './seo/index.js';
|
|
30
32
|
export type { RSSValidationResult } from './rss/index.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,YAAY,EACV,WAAW,EACX,SAAS,EACT,WAAW,EACX,YAAY,EACZ,WAAW,EACX,UAAU,EACV,OAAO,EACP,SAAS,EACT,SAAS,EACT,UAAU,EACV,YAAY,GACb,MAAM,kBAAkB,CAAC;AAG1B,YAAY,EACV,WAAW,EACX,SAAS,EACT,UAAU,EACV,mBAAmB,EACnB,UAAU,EACV,YAAY,EACZ,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,GACb,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EACV,aAAa,EACb,YAAY,EACZ,uBAAuB,EACvB,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAG1B,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAGtF,YAAY,EACV,YAAY,EACZ,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAG1F,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EACL,mBAAmB,EACnB,WAAW,EACX,qBAAqB,EACrB,uBAAuB,EACvB,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,EACvB,iBAAiB,EACjB,2BAA2B,EAC3B,UAAU,EACV,qBAAqB,EACrB,mBAAmB,EACnB,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,EAClB,UAAU,EACV,aAAa,EACb,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AAGxB,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAG1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAE7D"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,YAAY,EACV,WAAW,EACX,SAAS,EACT,WAAW,EACX,YAAY,EACZ,WAAW,EACX,UAAU,EACV,OAAO,EACP,SAAS,EACT,SAAS,EACT,UAAU,EACV,YAAY,GACb,MAAM,kBAAkB,CAAC;AAG1B,YAAY,EACV,WAAW,EACX,SAAS,EACT,UAAU,EACV,mBAAmB,EACnB,UAAU,EACV,YAAY,EACZ,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,GACb,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EACV,aAAa,EACb,YAAY,EACZ,uBAAuB,EACvB,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAG1B,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAGtF,YAAY,EACV,YAAY,EACZ,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAG1F,YAAY,EACV,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,aAAa,EACb,aAAa,EACb,aAAa,EACb,UAAU,EACV,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,qBAAqB,EACrB,SAAS,EACT,WAAW,GACZ,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,YAAY,EACZ,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAG5B,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EACL,mBAAmB,EACnB,WAAW,EACX,qBAAqB,EACrB,uBAAuB,EACvB,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,EACvB,iBAAiB,EACjB,2BAA2B,EAC3B,UAAU,EACV,qBAAqB,EACrB,mBAAmB,EACnB,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,EAClB,UAAU,EACV,aAAa,EACb,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AAGxB,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAG1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAE7D"}
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* ```
|
|
21
21
|
*/
|
|
22
22
|
export { build, createDevServer, createPreviewServer, invalidate } from './core/index.js';
|
|
23
|
+
export { createMetricRecorder, noopMetricRecorder, writeMetrics, formatMetricsSummary, } from './metrics/index.js';
|
|
23
24
|
export { generateSEOMetadata, generateSEO, generateOpenGraphTags, generateTwitterCardTags, generateSitemap, generateSitemapEntry, generateSitemapXml, generateSitemapIndexXml, generateRobotsTxt, generateRobotsTxtFromConfig, escapeHtml, generateRobotsContent, validateSEOMetadata, detectExistingSEOTags, normalizeUrlPath, resolveAbsoluteUrl, isValidUrl, autoInjectSEO, shouldAutoInject, } from './seo/index.js';
|
|
24
25
|
export { generateRSSFeed, generateRSSFeeds, validateRSSConfig, validateRSSFeedConfig, } from './rss/index.js';
|
|
25
26
|
// Re-export config and env utilities
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stati Build Metrics System
|
|
3
|
+
*
|
|
4
|
+
* Provides performance measurement and reporting for Stati builds.
|
|
5
|
+
* Uses Node.js built-ins only - no external dependencies.
|
|
6
|
+
*
|
|
7
|
+
* @example Basic Usage
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { createMetricRecorder, writeMetrics, formatMetricsSummary } from '@stati/core';
|
|
10
|
+
*
|
|
11
|
+
* // Create recorder (noop when disabled)
|
|
12
|
+
* const recorder = createMetricRecorder({ enabled: true });
|
|
13
|
+
*
|
|
14
|
+
* // Record phases with spans
|
|
15
|
+
* const endConfig = recorder.startSpan('configLoadMs');
|
|
16
|
+
* await loadConfig();
|
|
17
|
+
* endConfig();
|
|
18
|
+
*
|
|
19
|
+
* // Increment counters
|
|
20
|
+
* recorder.increment('renderedPages');
|
|
21
|
+
*
|
|
22
|
+
* // Finalize and write
|
|
23
|
+
* const metrics = recorder.finalize();
|
|
24
|
+
* await writeMetrics(metrics, { cacheDir: '.stati' });
|
|
25
|
+
*
|
|
26
|
+
* // Format for CLI
|
|
27
|
+
* const summary = formatMetricsSummary(metrics);
|
|
28
|
+
* summary.forEach(line => console.log(line));
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @packageDocumentation
|
|
32
|
+
*/
|
|
33
|
+
export type { BuildMetrics, MetricsMeta, MetricsFlags, MetricsTotals, MetricsPhases, MetricsCounts, MetricsISG, PageTiming, IncrementalMetrics, MetricRecorder, MetricRecorderOptions, PhaseName, CounterName, GaugeName, } from './types.js';
|
|
34
|
+
export { createMetricRecorder } from './recorder.js';
|
|
35
|
+
export { noopMetricRecorder } from './noop.js';
|
|
36
|
+
export { writeMetrics, formatMetricsSummary, generateMetricsFilename, DEFAULT_METRICS_DIR, isCI, getGitCommit, getGitBranch, getCpuCount, getPlatform, getArch, getNodeVersion, getMemoryUsage, } from './utils/index.js';
|
|
37
|
+
export type { WriteMetricsOptions, WriteMetricsResult } from './utils/index.js';
|
|
38
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/metrics/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAGH,YAAY,EACV,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,aAAa,EACb,aAAa,EACb,aAAa,EACb,UAAU,EACV,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,qBAAqB,EACrB,SAAS,EACT,WAAW,EACX,SAAS,GACV,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAGrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAG/C,OAAO,EAEL,YAAY,EACZ,oBAAoB,EACpB,uBAAuB,EACvB,mBAAmB,EAEnB,IAAI,EACJ,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,WAAW,EACX,OAAO,EACP,cAAc,EACd,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stati Build Metrics System
|
|
3
|
+
*
|
|
4
|
+
* Provides performance measurement and reporting for Stati builds.
|
|
5
|
+
* Uses Node.js built-ins only - no external dependencies.
|
|
6
|
+
*
|
|
7
|
+
* @example Basic Usage
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { createMetricRecorder, writeMetrics, formatMetricsSummary } from '@stati/core';
|
|
10
|
+
*
|
|
11
|
+
* // Create recorder (noop when disabled)
|
|
12
|
+
* const recorder = createMetricRecorder({ enabled: true });
|
|
13
|
+
*
|
|
14
|
+
* // Record phases with spans
|
|
15
|
+
* const endConfig = recorder.startSpan('configLoadMs');
|
|
16
|
+
* await loadConfig();
|
|
17
|
+
* endConfig();
|
|
18
|
+
*
|
|
19
|
+
* // Increment counters
|
|
20
|
+
* recorder.increment('renderedPages');
|
|
21
|
+
*
|
|
22
|
+
* // Finalize and write
|
|
23
|
+
* const metrics = recorder.finalize();
|
|
24
|
+
* await writeMetrics(metrics, { cacheDir: '.stati' });
|
|
25
|
+
*
|
|
26
|
+
* // Format for CLI
|
|
27
|
+
* const summary = formatMetricsSummary(metrics);
|
|
28
|
+
* summary.forEach(line => console.log(line));
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @packageDocumentation
|
|
32
|
+
*/
|
|
33
|
+
// Recorder factory
|
|
34
|
+
export { createMetricRecorder } from './recorder.js';
|
|
35
|
+
// Noop recorder singleton
|
|
36
|
+
export { noopMetricRecorder } from './noop.js';
|
|
37
|
+
// Utilities (file writing, system info)
|
|
38
|
+
export {
|
|
39
|
+
// Writer utilities
|
|
40
|
+
writeMetrics, formatMetricsSummary, generateMetricsFilename, DEFAULT_METRICS_DIR,
|
|
41
|
+
// System utilities
|
|
42
|
+
isCI, getGitCommit, getGitBranch, getCpuCount, getPlatform, getArch, getNodeVersion, getMemoryUsage, } from './utils/index.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Noop MetricRecorder implementation.
|
|
3
|
+
* Used when metrics collection is disabled - has zero overhead.
|
|
4
|
+
* (Null Object Pattern)
|
|
5
|
+
*/
|
|
6
|
+
import type { MetricRecorder } from './types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Singleton noop recorder instance.
|
|
9
|
+
* Use this when metrics are disabled for zero-overhead operation.
|
|
10
|
+
*/
|
|
11
|
+
export declare const noopMetricRecorder: MetricRecorder;
|
|
12
|
+
//# sourceMappingURL=noop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"noop.d.ts","sourceRoot":"","sources":["../../src/metrics/noop.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,cAAc,EAOf,MAAM,YAAY,CAAC;AAiGpB;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,cAAyC,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Noop MetricRecorder implementation.
|
|
3
|
+
* Used when metrics collection is disabled - has zero overhead.
|
|
4
|
+
* (Null Object Pattern)
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Empty function that does nothing.
|
|
8
|
+
*/
|
|
9
|
+
const noop = () => {
|
|
10
|
+
/* intentionally empty */
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Noop implementation of MetricRecorder.
|
|
14
|
+
* All methods are no-ops with minimal overhead.
|
|
15
|
+
*/
|
|
16
|
+
class NoopMetricRecorder {
|
|
17
|
+
enabled = false;
|
|
18
|
+
detailed = false;
|
|
19
|
+
startSpan(_name) {
|
|
20
|
+
return noop;
|
|
21
|
+
}
|
|
22
|
+
recordPhase(_name, _durationMs) {
|
|
23
|
+
/* noop */
|
|
24
|
+
}
|
|
25
|
+
addToPhase(_name, _durationMs) {
|
|
26
|
+
/* noop */
|
|
27
|
+
}
|
|
28
|
+
increment(_name, _amount) {
|
|
29
|
+
/* noop */
|
|
30
|
+
}
|
|
31
|
+
setGauge(_name, _value) {
|
|
32
|
+
/* noop */
|
|
33
|
+
}
|
|
34
|
+
recordPageTiming(_url, _durationMs, _cached, _templatesLoaded) {
|
|
35
|
+
/* noop */
|
|
36
|
+
}
|
|
37
|
+
snapshotMemory() {
|
|
38
|
+
/* noop */
|
|
39
|
+
}
|
|
40
|
+
setISGMetrics(_metrics) {
|
|
41
|
+
/* noop */
|
|
42
|
+
}
|
|
43
|
+
setIncrementalMetrics(_metrics) {
|
|
44
|
+
/* noop */
|
|
45
|
+
}
|
|
46
|
+
finalize() {
|
|
47
|
+
// Return minimal valid metrics object
|
|
48
|
+
return {
|
|
49
|
+
schemaVersion: '1',
|
|
50
|
+
meta: {
|
|
51
|
+
timestamp: new Date().toISOString(),
|
|
52
|
+
ci: false,
|
|
53
|
+
nodeVersion: process.version.replace(/^v/, ''),
|
|
54
|
+
platform: process.platform,
|
|
55
|
+
arch: process.arch,
|
|
56
|
+
cpuCount: 1,
|
|
57
|
+
statiVersion: 'unknown',
|
|
58
|
+
command: 'build',
|
|
59
|
+
flags: {},
|
|
60
|
+
},
|
|
61
|
+
totals: {
|
|
62
|
+
durationMs: 0,
|
|
63
|
+
peakRssBytes: 0,
|
|
64
|
+
heapUsedBytes: 0,
|
|
65
|
+
},
|
|
66
|
+
phases: {},
|
|
67
|
+
counts: {
|
|
68
|
+
totalPages: 0,
|
|
69
|
+
renderedPages: 0,
|
|
70
|
+
cachedPages: 0,
|
|
71
|
+
assetsCopied: 0,
|
|
72
|
+
templatesLoaded: 0,
|
|
73
|
+
markdownFilesProcessed: 0,
|
|
74
|
+
},
|
|
75
|
+
isg: {
|
|
76
|
+
enabled: false,
|
|
77
|
+
cacheHitRate: 0,
|
|
78
|
+
manifestEntries: 0,
|
|
79
|
+
invalidatedEntries: 0,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Singleton noop recorder instance.
|
|
86
|
+
* Use this when metrics are disabled for zero-overhead operation.
|
|
87
|
+
*/
|
|
88
|
+
export const noopMetricRecorder = new NoopMetricRecorder();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Active MetricRecorder implementation.
|
|
3
|
+
* Uses performance.now() from node:perf_hooks for high-resolution timing.
|
|
4
|
+
*/
|
|
5
|
+
import type { MetricRecorder, MetricRecorderOptions } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Create a MetricRecorder instance.
|
|
8
|
+
* Returns a noop recorder when disabled for zero overhead.
|
|
9
|
+
*
|
|
10
|
+
* @param options - Recorder configuration
|
|
11
|
+
* @returns MetricRecorder instance
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* // Create an enabled recorder
|
|
16
|
+
* const recorder = createMetricRecorder({ enabled: true });
|
|
17
|
+
*
|
|
18
|
+
* // Start a span
|
|
19
|
+
* const endSpan = recorder.startSpan('configLoadMs');
|
|
20
|
+
* await loadConfig();
|
|
21
|
+
* endSpan(); // Records duration
|
|
22
|
+
*
|
|
23
|
+
* // Increment counters
|
|
24
|
+
* recorder.increment('renderedPages');
|
|
25
|
+
*
|
|
26
|
+
* // Finalize and get metrics
|
|
27
|
+
* const metrics = recorder.finalize();
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare function createMetricRecorder(options?: MetricRecorderOptions): MetricRecorder;
|
|
31
|
+
//# sourceMappingURL=recorder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../src/metrics/recorder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EAQtB,MAAM,YAAY,CAAC;AAiNpB;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,qBAA0B,GAAG,cAAc,CAaxF"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Active MetricRecorder implementation.
|
|
3
|
+
* Uses performance.now() from node:perf_hooks for high-resolution timing.
|
|
4
|
+
*/
|
|
5
|
+
import { performance } from 'node:perf_hooks';
|
|
6
|
+
import { isCI, getGitCommit, getGitBranch, getCpuCount, getPlatform, getArch, getNodeVersion, getMemoryUsage, } from './utils/index.js';
|
|
7
|
+
import { noopMetricRecorder } from './noop.js';
|
|
8
|
+
/**
|
|
9
|
+
* Active metric recorder that collects build performance data.
|
|
10
|
+
*/
|
|
11
|
+
class ActiveMetricRecorder {
|
|
12
|
+
enabled = true;
|
|
13
|
+
detailed;
|
|
14
|
+
startTime;
|
|
15
|
+
command;
|
|
16
|
+
flags;
|
|
17
|
+
statiVersion;
|
|
18
|
+
phases = {};
|
|
19
|
+
counts = {
|
|
20
|
+
totalPages: 0,
|
|
21
|
+
renderedPages: 0,
|
|
22
|
+
cachedPages: 0,
|
|
23
|
+
assetsCopied: 0,
|
|
24
|
+
templatesLoaded: 0,
|
|
25
|
+
markdownFilesProcessed: 0,
|
|
26
|
+
};
|
|
27
|
+
isgMetrics = {
|
|
28
|
+
enabled: false,
|
|
29
|
+
cacheHitRate: 0,
|
|
30
|
+
manifestEntries: 0,
|
|
31
|
+
invalidatedEntries: 0,
|
|
32
|
+
};
|
|
33
|
+
incrementalMetrics;
|
|
34
|
+
pageTimings = [];
|
|
35
|
+
gauges = new Map();
|
|
36
|
+
constructor(options = {}) {
|
|
37
|
+
this.startTime = performance.now();
|
|
38
|
+
this.detailed = options.detailed ?? false;
|
|
39
|
+
this.command = options.command ?? 'build';
|
|
40
|
+
this.flags = options.flags ?? {};
|
|
41
|
+
this.statiVersion = options.statiVersion ?? 'unknown';
|
|
42
|
+
// Take initial memory snapshot
|
|
43
|
+
this.snapshotMemory();
|
|
44
|
+
}
|
|
45
|
+
startSpan(name) {
|
|
46
|
+
const spanStart = performance.now();
|
|
47
|
+
return () => {
|
|
48
|
+
const duration = performance.now() - spanStart;
|
|
49
|
+
this.recordPhase(name, duration);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
recordPhase(name, durationMs) {
|
|
53
|
+
// Record phase timing using the phase name directly (replaces)
|
|
54
|
+
this.phases[name] = durationMs;
|
|
55
|
+
}
|
|
56
|
+
addToPhase(name, durationMs) {
|
|
57
|
+
// Add to existing phase timing (accumulates)
|
|
58
|
+
const current = this.phases[name] ?? 0;
|
|
59
|
+
this.phases[name] = current + durationMs;
|
|
60
|
+
}
|
|
61
|
+
increment(name, amount = 1) {
|
|
62
|
+
const current = this.counts[name] ?? 0;
|
|
63
|
+
this.counts[name] = current + amount;
|
|
64
|
+
}
|
|
65
|
+
setGauge(name, value) {
|
|
66
|
+
// Gauges track the maximum value observed for a given name
|
|
67
|
+
const current = this.gauges.get(name) ?? 0;
|
|
68
|
+
this.gauges.set(name, Math.max(current, value));
|
|
69
|
+
}
|
|
70
|
+
recordPageTiming(url, durationMs, cached, templatesLoaded) {
|
|
71
|
+
if (this.detailed) {
|
|
72
|
+
const timing = { url, durationMs, cached };
|
|
73
|
+
if (templatesLoaded !== undefined) {
|
|
74
|
+
this.pageTimings.push({ ...timing, templatesLoaded });
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
this.pageTimings.push(timing);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
snapshotMemory() {
|
|
82
|
+
const usage = getMemoryUsage();
|
|
83
|
+
this.setGauge('peakRss', usage.rss);
|
|
84
|
+
}
|
|
85
|
+
setISGMetrics(metrics) {
|
|
86
|
+
this.isgMetrics = { ...this.isgMetrics, ...metrics };
|
|
87
|
+
}
|
|
88
|
+
setIncrementalMetrics(metrics) {
|
|
89
|
+
this.incrementalMetrics = metrics;
|
|
90
|
+
}
|
|
91
|
+
finalize() {
|
|
92
|
+
// Take final memory snapshot
|
|
93
|
+
this.snapshotMemory();
|
|
94
|
+
const finalMemory = getMemoryUsage();
|
|
95
|
+
const totalDuration = performance.now() - this.startTime;
|
|
96
|
+
// Build the meta object, excluding undefined git values
|
|
97
|
+
const gitCommit = getGitCommit();
|
|
98
|
+
const gitBranch = getGitBranch();
|
|
99
|
+
const meta = {
|
|
100
|
+
timestamp: new Date().toISOString(),
|
|
101
|
+
ci: isCI(),
|
|
102
|
+
nodeVersion: getNodeVersion(),
|
|
103
|
+
platform: getPlatform(),
|
|
104
|
+
arch: getArch(),
|
|
105
|
+
cpuCount: getCpuCount(),
|
|
106
|
+
statiVersion: this.statiVersion,
|
|
107
|
+
command: this.command,
|
|
108
|
+
flags: this.flags,
|
|
109
|
+
...(gitCommit !== undefined && { gitCommit }),
|
|
110
|
+
...(gitBranch !== undefined && { gitBranch }),
|
|
111
|
+
};
|
|
112
|
+
// Build the base metrics object
|
|
113
|
+
const baseMetrics = {
|
|
114
|
+
schemaVersion: '1',
|
|
115
|
+
meta,
|
|
116
|
+
totals: {
|
|
117
|
+
durationMs: totalDuration,
|
|
118
|
+
peakRssBytes: this.peakRss,
|
|
119
|
+
heapUsedBytes: finalMemory.heapUsed,
|
|
120
|
+
},
|
|
121
|
+
phases: this.phases,
|
|
122
|
+
counts: this.counts,
|
|
123
|
+
isg: this.isgMetrics,
|
|
124
|
+
};
|
|
125
|
+
// Add optional fields conditionally
|
|
126
|
+
const optionalFields = {};
|
|
127
|
+
if (this.detailed && this.pageTimings.length > 0) {
|
|
128
|
+
optionalFields.pageTimings = this.pageTimings;
|
|
129
|
+
}
|
|
130
|
+
if (this.incrementalMetrics) {
|
|
131
|
+
optionalFields.incremental = this.incrementalMetrics;
|
|
132
|
+
}
|
|
133
|
+
return { ...baseMetrics, ...optionalFields };
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Get the peak RSS value from gauges.
|
|
137
|
+
*/
|
|
138
|
+
get peakRss() {
|
|
139
|
+
return this.gauges.get('peakRss') ?? 0;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Create a MetricRecorder instance.
|
|
144
|
+
* Returns a noop recorder when disabled for zero overhead.
|
|
145
|
+
*
|
|
146
|
+
* @param options - Recorder configuration
|
|
147
|
+
* @returns MetricRecorder instance
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```typescript
|
|
151
|
+
* // Create an enabled recorder
|
|
152
|
+
* const recorder = createMetricRecorder({ enabled: true });
|
|
153
|
+
*
|
|
154
|
+
* // Start a span
|
|
155
|
+
* const endSpan = recorder.startSpan('configLoadMs');
|
|
156
|
+
* await loadConfig();
|
|
157
|
+
* endSpan(); // Records duration
|
|
158
|
+
*
|
|
159
|
+
* // Increment counters
|
|
160
|
+
* recorder.increment('renderedPages');
|
|
161
|
+
*
|
|
162
|
+
* // Finalize and get metrics
|
|
163
|
+
* const metrics = recorder.finalize();
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
export function createMetricRecorder(options = {}) {
|
|
167
|
+
if (options.enabled === false) {
|
|
168
|
+
return noopMetricRecorder;
|
|
169
|
+
}
|
|
170
|
+
// Check environment variable for CI metrics
|
|
171
|
+
const envEnabled = process.env.STATI_METRICS === '1' || process.env.STATI_METRICS === 'true';
|
|
172
|
+
if (!options.enabled && !envEnabled) {
|
|
173
|
+
return noopMetricRecorder;
|
|
174
|
+
}
|
|
175
|
+
return new ActiveMetricRecorder(options);
|
|
176
|
+
}
|