har-o-scope 0.1.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 (75) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +179 -0
  3. package/completions/har-o-scope.bash +64 -0
  4. package/completions/har-o-scope.fish +43 -0
  5. package/completions/har-o-scope.zsh +63 -0
  6. package/dist/cli/colors.d.ts +17 -0
  7. package/dist/cli/colors.d.ts.map +1 -0
  8. package/dist/cli/colors.js +54 -0
  9. package/dist/cli/demo.d.ts +7 -0
  10. package/dist/cli/demo.d.ts.map +1 -0
  11. package/dist/cli/demo.js +62 -0
  12. package/dist/cli/formatters.d.ts +12 -0
  13. package/dist/cli/formatters.d.ts.map +1 -0
  14. package/dist/cli/formatters.js +249 -0
  15. package/dist/cli/index.d.ts +3 -0
  16. package/dist/cli/index.d.ts.map +1 -0
  17. package/dist/cli/index.js +260 -0
  18. package/dist/cli/rules.d.ts +3 -0
  19. package/dist/cli/rules.d.ts.map +1 -0
  20. package/dist/cli/rules.js +36 -0
  21. package/dist/cli/sarif.d.ts +9 -0
  22. package/dist/cli/sarif.d.ts.map +1 -0
  23. package/dist/cli/sarif.js +104 -0
  24. package/dist/lib/analyze.d.ts +10 -0
  25. package/dist/lib/analyze.d.ts.map +1 -0
  26. package/dist/lib/analyze.js +83 -0
  27. package/dist/lib/classifier.d.ts +8 -0
  28. package/dist/lib/classifier.d.ts.map +1 -0
  29. package/dist/lib/classifier.js +74 -0
  30. package/dist/lib/diff.d.ts +15 -0
  31. package/dist/lib/diff.d.ts.map +1 -0
  32. package/dist/lib/diff.js +130 -0
  33. package/dist/lib/errors.d.ts +56 -0
  34. package/dist/lib/errors.d.ts.map +1 -0
  35. package/dist/lib/errors.js +65 -0
  36. package/dist/lib/evaluate.d.ts +19 -0
  37. package/dist/lib/evaluate.d.ts.map +1 -0
  38. package/dist/lib/evaluate.js +189 -0
  39. package/dist/lib/health-score.d.ts +18 -0
  40. package/dist/lib/health-score.d.ts.map +1 -0
  41. package/dist/lib/health-score.js +74 -0
  42. package/dist/lib/html-report.d.ts +15 -0
  43. package/dist/lib/html-report.d.ts.map +1 -0
  44. package/dist/lib/html-report.js +299 -0
  45. package/dist/lib/index.d.ts +26 -0
  46. package/dist/lib/index.d.ts.map +1 -0
  47. package/dist/lib/index.js +24 -0
  48. package/dist/lib/normalizer.d.ts +18 -0
  49. package/dist/lib/normalizer.d.ts.map +1 -0
  50. package/dist/lib/normalizer.js +201 -0
  51. package/dist/lib/rule-engine.d.ts +12 -0
  52. package/dist/lib/rule-engine.d.ts.map +1 -0
  53. package/dist/lib/rule-engine.js +122 -0
  54. package/dist/lib/sanitizer.d.ts +10 -0
  55. package/dist/lib/sanitizer.d.ts.map +1 -0
  56. package/dist/lib/sanitizer.js +129 -0
  57. package/dist/lib/schema.d.ts +85 -0
  58. package/dist/lib/schema.d.ts.map +1 -0
  59. package/dist/lib/schema.js +1 -0
  60. package/dist/lib/trace-sanitizer.d.ts +30 -0
  61. package/dist/lib/trace-sanitizer.d.ts.map +1 -0
  62. package/dist/lib/trace-sanitizer.js +85 -0
  63. package/dist/lib/types.d.ts +161 -0
  64. package/dist/lib/types.d.ts.map +1 -0
  65. package/dist/lib/types.js +1 -0
  66. package/dist/lib/unbatched-detect.d.ts +7 -0
  67. package/dist/lib/unbatched-detect.d.ts.map +1 -0
  68. package/dist/lib/unbatched-detect.js +59 -0
  69. package/dist/lib/validator.d.ts +4 -0
  70. package/dist/lib/validator.d.ts.map +1 -0
  71. package/dist/lib/validator.js +409 -0
  72. package/package.json +98 -0
  73. package/rules/generic/issue-rules.yaml +292 -0
  74. package/rules/generic/shared/base-conditions.yaml +28 -0
  75. package/rules/generic/shared/filters.yaml +12 -0
package/README.md ADDED
@@ -0,0 +1,179 @@
1
+ # har-o-scope
2
+
3
+ Zero-trust intelligent HAR file analyzer. Drop a HAR file, get instant diagnosis.
4
+
5
+ [![CI](https://github.com/vegaPDX/har-o-scope/actions/workflows/ci.yml/badge.svg)](https://github.com/vegaPDX/har-o-scope/actions/workflows/ci.yml)
6
+ [![npm](https://img.shields.io/npm/v/har-o-scope)](https://www.npmjs.com/package/har-o-scope)
7
+ [![License: AGPL-3.0](https://img.shields.io/badge/license-AGPL--3.0--only-blue.svg)](LICENSE)
8
+
9
+ Everything runs locally. No servers, no uploads. Your HAR data never leaves your machine.
10
+
11
+ ## What do you want to do?
12
+
13
+ | I want to... | Start here |
14
+ | ---------------------------------- | --------------------------------------- |
15
+ | **Analyze a HAR file** in my browser | [Browser UI](#browser-ui) |
16
+ | **Add HAR analysis to CI/CD** | [CLI](#cli) |
17
+ | **Write custom detection rules** | [Writing Custom Rules](#writing-custom-rules) |
18
+ | **Use it as a library** | [Library API](#library) |
19
+ | **Understand how it works** | [Architecture](#architecture) |
20
+
21
+ ## Browser UI
22
+
23
+ Open [har-o-scope](https://vegaPDX.github.io/har-o-scope/) and drop a `.har` file. You get:
24
+
25
+ - Health score (0-100) with root cause classification
26
+ - Request waterfall with timing breakdown
27
+ - Categorized findings with fix recommendations
28
+ - Before/after diff comparison
29
+ - Export to JSON, CSV, Markdown, or self-contained HTML
30
+
31
+ Works offline. Dark mode. Keyboard shortcuts (`?` to see them all).
32
+
33
+ ## CLI
34
+
35
+ ```bash
36
+ npx har-o-scope analyze recording.har
37
+ ```
38
+
39
+ ```
40
+ Health Score: 62/100 ######----
41
+ Root Cause: server (confidence: 68%)
42
+ Requests: 15 | Time: 31.2s | Analysis: 4ms
43
+
44
+ Findings (2 critical, 1 warning, 3 info)
45
+
46
+ X [critical] 5 requests with slow TTFB (> 800ms)
47
+ ...
48
+ ```
49
+
50
+ ### Commands
51
+
52
+ ```bash
53
+ # Output formats: text, json, markdown, html, sarif
54
+ har-o-scope analyze recording.har --format json
55
+
56
+ # CI mode with exit codes and GitHub annotations
57
+ har-o-scope analyze recording.har --ci --threshold 70
58
+
59
+ # Compare before/after
60
+ har-o-scope diff before.har after.har
61
+
62
+ # Strip secrets before sharing
63
+ har-o-scope sanitize recording.har -o clean.har
64
+
65
+ # Validate HAR structure and custom rules
66
+ har-o-scope validate recording.har
67
+
68
+ # Try it with built-in demo data
69
+ har-o-scope analyze --demo
70
+ ```
71
+
72
+ ### Exit codes
73
+
74
+ | Code | Meaning |
75
+ | ---- | ------------------------------------------ |
76
+ | `0` | Pass. No warnings or critical findings. |
77
+ | `1` | Warnings found. |
78
+ | `2` | Critical findings, or score below threshold. |
79
+
80
+ ### CI/CD Integration
81
+
82
+ SARIF output plugs directly into GitHub's Security tab:
83
+
84
+ ```bash
85
+ har-o-scope analyze recording.har --sarif > results.sarif
86
+ ```
87
+
88
+ See [`examples/github-action.yml`](examples/github-action.yml) for a complete GitHub Actions workflow.
89
+
90
+ ## Library
91
+
92
+ ```typescript
93
+ import { analyze } from 'har-o-scope'
94
+ import { readFile } from 'node:fs/promises'
95
+
96
+ const har = JSON.parse(await readFile('recording.har', 'utf-8'))
97
+ const result = analyze(har)
98
+
99
+ console.log(result.healthScore) // { score: 72, breakdown: { ... } }
100
+ console.log(result.findings) // [{ ruleId: 'slow-ttfb', severity: 'critical', ... }]
101
+ ```
102
+
103
+ Subpath imports for tree-shaking:
104
+
105
+ ```typescript
106
+ import { analyze } from 'har-o-scope/analyze'
107
+ import { diff } from 'har-o-scope/diff'
108
+ import { sanitize } from 'har-o-scope/sanitize'
109
+ import { validate } from 'har-o-scope/validate'
110
+ import { computeHealthScore } from 'har-o-scope/health-score'
111
+ ```
112
+
113
+ Full API docs: [docs/api/reference.md](docs/api/reference.md)
114
+
115
+ ## Writing Custom Rules
116
+
117
+ Rules are YAML files. A minimal rule:
118
+
119
+ ```yaml
120
+ rules:
121
+ my-slow-api:
122
+ category: server
123
+ severity: warning
124
+ title: "{count} slow API call{s}"
125
+ description: "API requests took over 2 seconds."
126
+ recommendation: "Check server logs."
127
+ condition:
128
+ match_all:
129
+ - field: "entry.request.url"
130
+ matches: "/api/"
131
+ - field: "timings.wait"
132
+ gt: 2000
133
+ ```
134
+
135
+ Use it:
136
+
137
+ ```bash
138
+ har-o-scope analyze recording.har --rules my-rules.yaml
139
+ ```
140
+
141
+ 17 built-in rules ship in `rules/generic/`. You can compose custom rules that inherit shared conditions and exclude noise filters.
142
+
143
+ Learn more:
144
+ - [Tutorial: Your First Rule](docs/rules/tutorial.md)
145
+ - [Cookbook: Common Patterns](docs/rules/cookbook.md)
146
+ - [Reference: All Fields and Operators](docs/rules/reference.md)
147
+
148
+ ## Architecture
149
+
150
+ ```
151
+ HAR JSON ──> Normalizer ──> Rule Engine ──> Classifier ──> Health Score
152
+ | |
153
+ v v
154
+ Validator Findings[]
155
+ ```
156
+
157
+ The analysis pipeline:
158
+
159
+ 1. **Normalizer** parses HAR JSON, normalizes timings (replacing `-1` with `0`), classifies resource types, computes transfer sizes.
160
+ 2. **Rule Engine** evaluates YAML rules against each normalized entry. Rules support conditions, inheritance, filters, severity escalation, and aggregate detection.
161
+ 3. **Classifier** determines root cause (client, network, or server) using weighted scoring from rule findings.
162
+ 4. **Health Score** computes a 0-100 score with per-category deductions, timing penalties, and volume penalties.
163
+
164
+ Browser: runs in a Web Worker. CLI/library: runs on the main thread. No network requests anywhere.
165
+
166
+ 17 built-in rules detect: slow TTFB, stalled requests, DNS/TLS issues, missing compression, broken resources, HTTP/1.1 downgrades, missing cache headers, large payloads, redirect chains, CORS preflights, mixed content, and excessive requests.
167
+
168
+ ## Requirements
169
+
170
+ - **CLI and library:** Node.js >= 20
171
+ - **Browser UI:** Any modern browser (Chrome, Firefox, Safari, Edge)
172
+
173
+ ## Contributing
174
+
175
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, testing, and rule contribution guidelines.
176
+
177
+ ## License
178
+
179
+ [AGPL-3.0-only](LICENSE)
@@ -0,0 +1,64 @@
1
+ # bash completion for har-o-scope
2
+ # Install: source completions/har-o-scope.bash
3
+ # Or copy to /etc/bash_completion.d/har-o-scope
4
+
5
+ _har_o_scope() {
6
+ local cur prev commands analyze_opts diff_opts sanitize_opts validate_opts
7
+ COMPREPLY=()
8
+ cur="${COMP_WORDS[COMP_CWORD]}"
9
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
10
+
11
+ commands="analyze diff sanitize validate help"
12
+ analyze_opts="--format --sarif --ci --baseline --threshold --verbose --rules --demo --no-color --help"
13
+ diff_opts="--format --no-color --help"
14
+ sanitize_opts="--output --mode --help"
15
+ validate_opts="--rules --help"
16
+
17
+ case "${COMP_WORDS[1]}" in
18
+ analyze)
19
+ if [[ "$cur" == -* ]]; then
20
+ COMPREPLY=( $(compgen -W "$analyze_opts" -- "$cur") )
21
+ elif [[ "$prev" == "--format" ]]; then
22
+ COMPREPLY=( $(compgen -W "text json markdown" -- "$cur") )
23
+ elif [[ "$prev" == "--mode" ]]; then
24
+ COMPREPLY=( $(compgen -W "aggressive selective" -- "$cur") )
25
+ else
26
+ COMPREPLY=( $(compgen -f -X '!*.har' -- "$cur") )
27
+ fi
28
+ ;;
29
+ diff)
30
+ if [[ "$cur" == -* ]]; then
31
+ COMPREPLY=( $(compgen -W "$diff_opts" -- "$cur") )
32
+ elif [[ "$prev" == "--format" ]]; then
33
+ COMPREPLY=( $(compgen -W "text json markdown" -- "$cur") )
34
+ else
35
+ COMPREPLY=( $(compgen -f -X '!*.har' -- "$cur") )
36
+ fi
37
+ ;;
38
+ sanitize)
39
+ if [[ "$cur" == -* ]]; then
40
+ COMPREPLY=( $(compgen -W "$sanitize_opts" -- "$cur") )
41
+ elif [[ "$prev" == "--mode" ]]; then
42
+ COMPREPLY=( $(compgen -W "aggressive selective" -- "$cur") )
43
+ else
44
+ COMPREPLY=( $(compgen -f -X '!*.har' -- "$cur") )
45
+ fi
46
+ ;;
47
+ validate)
48
+ if [[ "$cur" == -* ]]; then
49
+ COMPREPLY=( $(compgen -W "$validate_opts" -- "$cur") )
50
+ else
51
+ COMPREPLY=( $(compgen -f -X '!*.har' -X '!*.yaml' -X '!*.yml' -- "$cur") )
52
+ fi
53
+ ;;
54
+ *)
55
+ if [[ "$cur" == -* ]]; then
56
+ COMPREPLY=( $(compgen -W "--version --help" -- "$cur") )
57
+ else
58
+ COMPREPLY=( $(compgen -W "$commands" -- "$cur") )
59
+ fi
60
+ ;;
61
+ esac
62
+ }
63
+
64
+ complete -F _har_o_scope har-o-scope
@@ -0,0 +1,43 @@
1
+ # fish completion for har-o-scope
2
+ # Install: source completions/har-o-scope.fish
3
+ # Or copy to ~/.config/fish/completions/
4
+
5
+ # Disable file completion by default
6
+ complete -c har-o-scope -f
7
+
8
+ # Global options
9
+ complete -c har-o-scope -s V -l version -d 'Output version number'
10
+ complete -c har-o-scope -s h -l help -d 'Display help'
11
+
12
+ # Subcommands
13
+ complete -c har-o-scope -n __fish_use_subcommand -a analyze -d 'Analyze a HAR file'
14
+ complete -c har-o-scope -n __fish_use_subcommand -a diff -d 'Compare two HAR files'
15
+ complete -c har-o-scope -n __fish_use_subcommand -a sanitize -d 'Strip secrets from a HAR file'
16
+ complete -c har-o-scope -n __fish_use_subcommand -a validate -d 'Validate HAR structure and rules'
17
+ complete -c har-o-scope -n __fish_use_subcommand -a help -d 'Display help for a command'
18
+
19
+ # analyze options
20
+ complete -c har-o-scope -n '__fish_seen_subcommand_from analyze' -s f -l format -x -a 'text json markdown' -d 'Output format'
21
+ complete -c har-o-scope -n '__fish_seen_subcommand_from analyze' -l sarif -d 'Output SARIF 2.1.0 JSON'
22
+ complete -c har-o-scope -n '__fish_seen_subcommand_from analyze' -l ci -d 'GitHub-compatible annotations'
23
+ complete -c har-o-scope -n '__fish_seen_subcommand_from analyze' -l baseline -r -F -d 'Baseline HAR file'
24
+ complete -c har-o-scope -n '__fish_seen_subcommand_from analyze' -l threshold -x -d 'Minimum health score'
25
+ complete -c har-o-scope -n '__fish_seen_subcommand_from analyze' -l verbose -d 'Show per-entry details'
26
+ complete -c har-o-scope -n '__fish_seen_subcommand_from analyze' -l rules -r -F -d 'Custom YAML rules'
27
+ complete -c har-o-scope -n '__fish_seen_subcommand_from analyze' -l demo -d 'Use built-in demo HAR'
28
+ complete -c har-o-scope -n '__fish_seen_subcommand_from analyze' -l no-color -d 'Disable colors'
29
+ complete -c har-o-scope -n '__fish_seen_subcommand_from analyze' -F
30
+
31
+ # diff options
32
+ complete -c har-o-scope -n '__fish_seen_subcommand_from diff' -s f -l format -x -a 'text json markdown' -d 'Output format'
33
+ complete -c har-o-scope -n '__fish_seen_subcommand_from diff' -l no-color -d 'Disable colors'
34
+ complete -c har-o-scope -n '__fish_seen_subcommand_from diff' -F
35
+
36
+ # sanitize options
37
+ complete -c har-o-scope -n '__fish_seen_subcommand_from sanitize' -s o -l output -r -F -d 'Output file'
38
+ complete -c har-o-scope -n '__fish_seen_subcommand_from sanitize' -l mode -x -a 'aggressive selective' -d 'Sanitization mode'
39
+ complete -c har-o-scope -n '__fish_seen_subcommand_from sanitize' -F
40
+
41
+ # validate options
42
+ complete -c har-o-scope -n '__fish_seen_subcommand_from validate' -l rules -r -F -d 'Custom YAML rules to validate'
43
+ complete -c har-o-scope -n '__fish_seen_subcommand_from validate' -F
@@ -0,0 +1,63 @@
1
+ #compdef har-o-scope
2
+ # zsh completion for har-o-scope
3
+ # Install: source completions/har-o-scope.zsh
4
+ # Or copy to a directory in your $fpath
5
+
6
+ _har_o_scope() {
7
+ local -a commands
8
+ commands=(
9
+ 'analyze:Analyze a HAR file for performance and security issues'
10
+ 'diff:Compare two HAR files for regressions'
11
+ 'sanitize:Strip secrets and sensitive data from a HAR file'
12
+ 'validate:Validate HAR file structure and custom rules'
13
+ 'help:Display help for a command'
14
+ )
15
+
16
+ _arguments -C \
17
+ '(-V --version)'{-V,--version}'[Output version number]' \
18
+ '(-h --help)'{-h,--help}'[Display help]' \
19
+ '1:command:->command' \
20
+ '*::arg:->args'
21
+
22
+ case $state in
23
+ command)
24
+ _describe 'command' commands
25
+ ;;
26
+ args)
27
+ case $words[1] in
28
+ analyze)
29
+ _arguments \
30
+ '(-f --format)'{-f,--format}'[Output format]:format:(text json markdown)' \
31
+ '--sarif[Output SARIF 2.1.0 JSON]' \
32
+ '--ci[Output GitHub-compatible annotations]' \
33
+ '--baseline[Compare against baseline HAR]:file:_files -g "*.har"' \
34
+ '--threshold[Minimum health score]:score:' \
35
+ '--verbose[Show per-entry timing details]' \
36
+ '--rules[Custom YAML rules]:path:_files -g "*.{yaml,yml}"' \
37
+ '--demo[Analyze built-in demo HAR]' \
38
+ '--no-color[Disable colored output]' \
39
+ '*:file:_files -g "*.har"'
40
+ ;;
41
+ diff)
42
+ _arguments \
43
+ '(-f --format)'{-f,--format}'[Output format]:format:(text json markdown)' \
44
+ '--no-color[Disable colored output]' \
45
+ '*:file:_files -g "*.har"'
46
+ ;;
47
+ sanitize)
48
+ _arguments \
49
+ '(-o --output)'{-o,--output}'[Output file]:file:_files' \
50
+ '--mode[Sanitization mode]:mode:(aggressive selective)' \
51
+ '*:file:_files -g "*.har"'
52
+ ;;
53
+ validate)
54
+ _arguments \
55
+ '--rules[Custom YAML rules to validate]:path:_files -g "*.{yaml,yml}"' \
56
+ '*:file:_files -g "*.{har,yaml,yml}"'
57
+ ;;
58
+ esac
59
+ ;;
60
+ esac
61
+ }
62
+
63
+ _har_o_scope "$@"
@@ -0,0 +1,17 @@
1
+ /**
2
+ * ANSI color utilities with TTY detection.
3
+ * Respects NO_COLOR env var and --no-color flag.
4
+ */
5
+ export declare function setColorEnabled(enabled: boolean): void;
6
+ export declare const red: (t: string) => string;
7
+ export declare const yellow: (t: string) => string;
8
+ export declare const green: (t: string) => string;
9
+ export declare const blue: (t: string) => string;
10
+ export declare const cyan: (t: string) => string;
11
+ export declare const dim: (t: string) => string;
12
+ export declare const bold: (t: string) => string;
13
+ export declare function severityColor(severity: string): (t: string) => string;
14
+ export declare function severityIcon(severity: string): string;
15
+ export declare function healthScoreColor(score: number): (t: string) => string;
16
+ export declare function scoreBar(score: number): string;
17
+ //# sourceMappingURL=colors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"colors.d.ts","sourceRoot":"","sources":["../../src/cli/colors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAEtD;AAWD,eAAO,MAAM,GAAG,GAAI,GAAG,MAAM,WAAgB,CAAA;AAC7C,eAAO,MAAM,MAAM,GAAI,GAAG,MAAM,WAAgB,CAAA;AAChD,eAAO,MAAM,KAAK,GAAI,GAAG,MAAM,WAAgB,CAAA;AAC/C,eAAO,MAAM,IAAI,GAAI,GAAG,MAAM,WAAgB,CAAA;AAC9C,eAAO,MAAM,IAAI,GAAI,GAAG,MAAM,WAAgB,CAAA;AAC9C,eAAO,MAAM,GAAG,GAAI,GAAG,MAAM,WAAe,CAAA;AAC5C,eAAO,MAAM,IAAI,GAAI,GAAG,MAAM,WAAe,CAAA;AAE7C,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAOrE;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAOrD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAIrE;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAO9C"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * ANSI color utilities with TTY detection.
3
+ * Respects NO_COLOR env var and --no-color flag.
4
+ */
5
+ let _forceColor;
6
+ export function setColorEnabled(enabled) {
7
+ _forceColor = enabled;
8
+ }
9
+ function isEnabled() {
10
+ if (_forceColor !== undefined)
11
+ return _forceColor;
12
+ return !!process.stdout.isTTY && !process.env.NO_COLOR;
13
+ }
14
+ function wrap(code, text) {
15
+ return isEnabled() ? `\x1b[${code}m${text}\x1b[0m` : text;
16
+ }
17
+ export const red = (t) => wrap(31, t);
18
+ export const yellow = (t) => wrap(33, t);
19
+ export const green = (t) => wrap(32, t);
20
+ export const blue = (t) => wrap(34, t);
21
+ export const cyan = (t) => wrap(36, t);
22
+ export const dim = (t) => wrap(2, t);
23
+ export const bold = (t) => wrap(1, t);
24
+ export function severityColor(severity) {
25
+ switch (severity) {
26
+ case 'critical': return red;
27
+ case 'warning': return yellow;
28
+ case 'info': return blue;
29
+ default: return (t) => t;
30
+ }
31
+ }
32
+ export function severityIcon(severity) {
33
+ switch (severity) {
34
+ case 'critical': return isEnabled() ? '\u2716' : 'X';
35
+ case 'warning': return isEnabled() ? '\u25B2' : '!';
36
+ case 'info': return isEnabled() ? '\u25CF' : 'i';
37
+ default: return '-';
38
+ }
39
+ }
40
+ export function healthScoreColor(score) {
41
+ if (score >= 90)
42
+ return green;
43
+ if (score >= 50)
44
+ return yellow;
45
+ return red;
46
+ }
47
+ export function scoreBar(score) {
48
+ const filled = Math.round(score / 10);
49
+ const empty = 10 - filled;
50
+ if (isEnabled()) {
51
+ return '\u2588'.repeat(filled) + '\u2591'.repeat(empty);
52
+ }
53
+ return '#'.repeat(filled) + '-'.repeat(empty);
54
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Embedded demo HAR for --demo flag.
3
+ * Designed to trigger multiple rules for a representative output.
4
+ */
5
+ import type { Har } from 'har-format';
6
+ export declare const demoHar: Har;
7
+ //# sourceMappingURL=demo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"demo.d.ts","sourceRoot":"","sources":["../../src/cli/demo.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,GAAG,EAAS,MAAM,YAAY,CAAA;AAgD5C,eAAO,MAAM,OAAO,EAAE,GAqCrB,CAAA"}
@@ -0,0 +1,62 @@
1
+ const BASE_TIME = '2024-06-15T10:00:00.000Z';
2
+ function entry(offset, overrides) {
3
+ const { url = 'https://demo.example.com/api/data', status = 200, method = 'GET', mimeType = 'application/json', wait = 80, blocked = 2, dns = 0, connect = 0, ssl = 0, bodySize = 1200, httpVersion = 'h2', headers = [], } = overrides;
4
+ const time = new Date(new Date(BASE_TIME).getTime() + offset);
5
+ return {
6
+ startedDateTime: time.toISOString(),
7
+ time: blocked + dns + connect + ssl + 1 + wait + 10,
8
+ request: {
9
+ method, url, httpVersion,
10
+ headers: [],
11
+ queryString: [],
12
+ cookies: [],
13
+ headersSize: -1,
14
+ bodySize: -1,
15
+ },
16
+ response: {
17
+ status, statusText: status < 400 ? 'OK' : 'Error', httpVersion,
18
+ headers: [
19
+ { name: 'Content-Type', value: mimeType },
20
+ ...headers,
21
+ ],
22
+ cookies: [],
23
+ content: { size: bodySize, mimeType },
24
+ redirectURL: '',
25
+ headersSize: -1,
26
+ bodySize,
27
+ },
28
+ cache: {},
29
+ timings: { blocked, dns, connect, ssl, send: 1, wait, receive: 10 },
30
+ };
31
+ }
32
+ export const demoHar = {
33
+ log: {
34
+ version: '1.2',
35
+ creator: { name: 'har-o-scope-demo', version: '1.0.0' },
36
+ entries: [
37
+ // Document load
38
+ entry(0, { url: 'https://demo.example.com/', mimeType: 'text/html', wait: 250, dns: 25, connect: 40, ssl: 35, bodySize: 15000 }),
39
+ // CSS + JS (normal)
40
+ entry(350, { url: 'https://demo.example.com/assets/main.css', mimeType: 'text/css', bodySize: 8000 }),
41
+ entry(360, { url: 'https://demo.example.com/assets/app.js', mimeType: 'application/javascript', bodySize: 95000 }),
42
+ entry(370, { url: 'https://demo.example.com/assets/vendor.js', mimeType: 'application/javascript', bodySize: 280000 }),
43
+ // API calls: 3 with slow TTFB (triggers slow-ttfb)
44
+ entry(500, { url: 'https://demo.example.com/api/users', wait: 1200 }),
45
+ entry(520, { url: 'https://demo.example.com/api/orders', wait: 2400 }),
46
+ entry(540, { url: 'https://demo.example.com/api/dashboard/stats', wait: 950, bodySize: 4500 }),
47
+ // Server errors (triggers broken-resources)
48
+ entry(800, { url: 'https://demo.example.com/api/reports/generate', status: 500, wait: 3100 }),
49
+ entry(1200, { url: 'https://demo.example.com/api/notifications', status: 503, wait: 30000 }),
50
+ // Large payload (triggers large-payload)
51
+ entry(600, { url: 'https://demo.example.com/api/export/full', wait: 450, bodySize: 2_500_000 }),
52
+ // HTTP/1.1 third-party (triggers http1-downgrade)
53
+ entry(400, { url: 'https://analytics.third-party.com/collect', httpVersion: 'HTTP/1.1', bodySize: 200 }),
54
+ entry(410, { url: 'https://analytics.third-party.com/track', httpVersion: 'HTTP/1.1', bodySize: 150 }),
55
+ // Normal images
56
+ entry(450, { url: 'https://demo.example.com/images/logo.svg', mimeType: 'image/svg+xml', bodySize: 3000 }),
57
+ entry(460, { url: 'https://demo.example.com/images/hero.webp', mimeType: 'image/webp', bodySize: 45000 }),
58
+ // Font
59
+ entry(380, { url: 'https://demo.example.com/fonts/inter.woff2', mimeType: 'font/woff2', bodySize: 24000 }),
60
+ ],
61
+ },
62
+ };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Output formatters for CLI: text, json, markdown, ci annotations.
3
+ */
4
+ import type { AnalysisResult, HealthScore, DiffResult } from '../lib/types.js';
5
+ export declare function formatText(result: AnalysisResult, score: HealthScore, verbose: boolean): string;
6
+ export declare function formatJson(result: AnalysisResult, score: HealthScore): string;
7
+ export declare function formatMarkdown(result: AnalysisResult, score: HealthScore): string;
8
+ export declare function formatCi(result: AnalysisResult, score: HealthScore, threshold: number): string;
9
+ export declare function formatDiffText(diff: DiffResult): string;
10
+ export declare function formatDiffJson(diff: DiffResult): string;
11
+ export declare function formatDiffMarkdown(diff: DiffResult): string;
12
+ //# sourceMappingURL=formatters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatters.d.ts","sourceRoot":"","sources":["../../src/cli/formatters.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAW,MAAM,iBAAiB,CAAA;AA+BvF,wBAAgB,UAAU,CACxB,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,OAAO,GACf,MAAM,CAyER;AAID,wBAAgB,UAAU,CACxB,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,WAAW,GACjB,MAAM,CAmBR;AAID,wBAAgB,cAAc,CAC5B,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,WAAW,GACjB,MAAM,CA2CR;AAID,wBAAgB,QAAQ,CAAC,MAAM,EAAE,cAAc,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAgB9F;AAID,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAiDvD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAEvD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAiC3D"}