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.
- package/LICENSE +661 -0
- package/README.md +179 -0
- package/completions/har-o-scope.bash +64 -0
- package/completions/har-o-scope.fish +43 -0
- package/completions/har-o-scope.zsh +63 -0
- package/dist/cli/colors.d.ts +17 -0
- package/dist/cli/colors.d.ts.map +1 -0
- package/dist/cli/colors.js +54 -0
- package/dist/cli/demo.d.ts +7 -0
- package/dist/cli/demo.d.ts.map +1 -0
- package/dist/cli/demo.js +62 -0
- package/dist/cli/formatters.d.ts +12 -0
- package/dist/cli/formatters.d.ts.map +1 -0
- package/dist/cli/formatters.js +249 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +260 -0
- package/dist/cli/rules.d.ts +3 -0
- package/dist/cli/rules.d.ts.map +1 -0
- package/dist/cli/rules.js +36 -0
- package/dist/cli/sarif.d.ts +9 -0
- package/dist/cli/sarif.d.ts.map +1 -0
- package/dist/cli/sarif.js +104 -0
- package/dist/lib/analyze.d.ts +10 -0
- package/dist/lib/analyze.d.ts.map +1 -0
- package/dist/lib/analyze.js +83 -0
- package/dist/lib/classifier.d.ts +8 -0
- package/dist/lib/classifier.d.ts.map +1 -0
- package/dist/lib/classifier.js +74 -0
- package/dist/lib/diff.d.ts +15 -0
- package/dist/lib/diff.d.ts.map +1 -0
- package/dist/lib/diff.js +130 -0
- package/dist/lib/errors.d.ts +56 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +65 -0
- package/dist/lib/evaluate.d.ts +19 -0
- package/dist/lib/evaluate.d.ts.map +1 -0
- package/dist/lib/evaluate.js +189 -0
- package/dist/lib/health-score.d.ts +18 -0
- package/dist/lib/health-score.d.ts.map +1 -0
- package/dist/lib/health-score.js +74 -0
- package/dist/lib/html-report.d.ts +15 -0
- package/dist/lib/html-report.d.ts.map +1 -0
- package/dist/lib/html-report.js +299 -0
- package/dist/lib/index.d.ts +26 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +24 -0
- package/dist/lib/normalizer.d.ts +18 -0
- package/dist/lib/normalizer.d.ts.map +1 -0
- package/dist/lib/normalizer.js +201 -0
- package/dist/lib/rule-engine.d.ts +12 -0
- package/dist/lib/rule-engine.d.ts.map +1 -0
- package/dist/lib/rule-engine.js +122 -0
- package/dist/lib/sanitizer.d.ts +10 -0
- package/dist/lib/sanitizer.d.ts.map +1 -0
- package/dist/lib/sanitizer.js +129 -0
- package/dist/lib/schema.d.ts +85 -0
- package/dist/lib/schema.d.ts.map +1 -0
- package/dist/lib/schema.js +1 -0
- package/dist/lib/trace-sanitizer.d.ts +30 -0
- package/dist/lib/trace-sanitizer.d.ts.map +1 -0
- package/dist/lib/trace-sanitizer.js +85 -0
- package/dist/lib/types.d.ts +161 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +1 -0
- package/dist/lib/unbatched-detect.d.ts +7 -0
- package/dist/lib/unbatched-detect.d.ts.map +1 -0
- package/dist/lib/unbatched-detect.js +59 -0
- package/dist/lib/validator.d.ts +4 -0
- package/dist/lib/validator.d.ts.map +1 -0
- package/dist/lib/validator.js +409 -0
- package/package.json +98 -0
- package/rules/generic/issue-rules.yaml +292 -0
- package/rules/generic/shared/base-conditions.yaml +28 -0
- 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
|
+
[](https://github.com/vegaPDX/har-o-scope/actions/workflows/ci.yml)
|
|
6
|
+
[](https://www.npmjs.com/package/har-o-scope)
|
|
7
|
+
[](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 @@
|
|
|
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"}
|
package/dist/cli/demo.js
ADDED
|
@@ -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"}
|