ssrwire 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 (74) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/CONTRIBUTING.md +49 -0
  3. package/LICENSE +21 -0
  4. package/PUBLISHING.md +134 -0
  5. package/README.md +403 -0
  6. package/SECURITY.md +26 -0
  7. package/dist/agents.d.ts +14 -0
  8. package/dist/agents.d.ts.map +1 -0
  9. package/dist/agents.js +100 -0
  10. package/dist/agents.js.map +1 -0
  11. package/dist/analyze.d.ts +4 -0
  12. package/dist/analyze.d.ts.map +1 -0
  13. package/dist/analyze.js +494 -0
  14. package/dist/analyze.js.map +1 -0
  15. package/dist/audit.d.ts +3 -0
  16. package/dist/audit.d.ts.map +1 -0
  17. package/dist/audit.js +69 -0
  18. package/dist/audit.js.map +1 -0
  19. package/dist/bin.d.ts +3 -0
  20. package/dist/bin.d.ts.map +1 -0
  21. package/dist/bin.js +4 -0
  22. package/dist/bin.js.map +1 -0
  23. package/dist/cli.d.ts +2 -0
  24. package/dist/cli.d.ts.map +1 -0
  25. package/dist/cli.js +173 -0
  26. package/dist/cli.js.map +1 -0
  27. package/dist/config.d.ts +17 -0
  28. package/dist/config.d.ts.map +1 -0
  29. package/dist/config.js +262 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/http-probe.d.ts +7 -0
  32. package/dist/http-probe.d.ts.map +1 -0
  33. package/dist/http-probe.js +406 -0
  34. package/dist/http-probe.js.map +1 -0
  35. package/dist/index.d.ts +11 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +10 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/redact.d.ts +10 -0
  40. package/dist/redact.d.ts.map +1 -0
  41. package/dist/redact.js +154 -0
  42. package/dist/redact.js.map +1 -0
  43. package/dist/reporters.d.ts +9 -0
  44. package/dist/reporters.d.ts.map +1 -0
  45. package/dist/reporters.js +220 -0
  46. package/dist/reporters.js.map +1 -0
  47. package/dist/stream-parser.d.ts +9 -0
  48. package/dist/stream-parser.d.ts.map +1 -0
  49. package/dist/stream-parser.js +366 -0
  50. package/dist/stream-parser.js.map +1 -0
  51. package/dist/types.d.ts +134 -0
  52. package/dist/types.d.ts.map +1 -0
  53. package/dist/types.js +2 -0
  54. package/dist/types.js.map +1 -0
  55. package/dist/version.d.ts +2 -0
  56. package/dist/version.d.ts.map +1 -0
  57. package/dist/version.js +14 -0
  58. package/dist/version.js.map +1 -0
  59. package/examples/github-actions.yml +73 -0
  60. package/examples/ssrwire.config.yml +37 -0
  61. package/package.json +91 -0
  62. package/src/agents.ts +129 -0
  63. package/src/analyze.ts +628 -0
  64. package/src/audit.ts +89 -0
  65. package/src/bin.ts +5 -0
  66. package/src/cli.ts +207 -0
  67. package/src/config.ts +313 -0
  68. package/src/http-probe.ts +461 -0
  69. package/src/index.ts +34 -0
  70. package/src/redact.ts +173 -0
  71. package/src/reporters.ts +274 -0
  72. package/src/stream-parser.ts +424 -0
  73. package/src/types.ts +160 -0
  74. package/src/version.ts +19 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # Changelog
2
+
3
+ All notable changes to SSRWire are documented here. The project follows
4
+ [Semantic Versioning](https://semver.org/).
5
+
6
+ ## [Unreleased]
7
+
8
+ ## [0.1.0] - 2026-08-22
9
+
10
+ ### Added
11
+
12
+ - Stream-observation probes for browser and crawler user-agent profiles.
13
+ - Timing and byte-position evidence for SEO metadata, H1, main text, and JSON-LD.
14
+ - Redirect, status, response-header, truncation, timeout, and network-failure evidence.
15
+ - Cross-agent delivery comparisons with configurable expectations.
16
+ - Terminal, JSON, and SARIF reports with CI-safe exit codes.
17
+ - YAML configuration, one-off URL checks, Docker support, and GitHub Actions examples.
18
+
19
+ [Unreleased]: https://github.com/lame13/ssrwire/compare/v0.1.0...HEAD
20
+ [0.1.0]: https://github.com/lame13/ssrwire/releases/tag/v0.1.0
@@ -0,0 +1,49 @@
1
+ # Contributing to SSRWire
2
+
3
+ Bug reports and focused pull requests are welcome. SSRWire stays useful by
4
+ remaining small, deterministic, and honest about what an HTTP client can
5
+ observe.
6
+
7
+ ## Development setup
8
+
9
+ Requirements:
10
+
11
+ - Node.js 22.12.0 or newer
12
+ - npm 10 or newer
13
+
14
+ ```bash
15
+ git clone https://github.com/lame13/ssrwire.git
16
+ cd ssrwire
17
+ npm ci
18
+ npm run check
19
+ ```
20
+
21
+ Use `npm run dev -- --help` while developing the CLI. Run `npm run format`
22
+ before opening a pull request, then run `npm run check` again.
23
+
24
+ ## Pull requests
25
+
26
+ Keep each change narrow. Include tests for behavior changes, especially for:
27
+
28
+ - response chunk boundaries and elements split across chunks;
29
+ - redirects, timeouts, aborted bodies, and size limits;
30
+ - malformed, duplicated, late, or body-located metadata;
31
+ - user-agent differences and comparison findings;
32
+ - redaction of configured header values;
33
+ - terminal, JSON, SARIF, and exit-code behavior.
34
+
35
+ Do not make timing tests depend on exact millisecond values. Shared runners and
36
+ local machines have unavoidable scheduling variance. Test ordering,
37
+ presence/absence, bounds with generous margins, and deterministic byte
38
+ positions instead.
39
+
40
+ ## Scope
41
+
42
+ SSRWire is an HTTP stream observer, not a browser, crawler, packet capture,
43
+ performance benchmark, or framework plugin. New features should preserve that
44
+ boundary. In particular, avoid conclusions that claim to reveal the server's
45
+ original flush calls: proxies, compression, TLS, HTTP stacks, and client
46
+ buffering can coalesce data before SSRWire sees it.
47
+
48
+ By contributing, you agree that your contribution is licensed under the MIT
49
+ License.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Niko M.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/PUBLISHING.md ADDED
@@ -0,0 +1,134 @@
1
+ # Publish SSRWire from a local machine
2
+
3
+ This repository intentionally includes no npm publishing workflow. Every npm
4
+ release is published from a local interactive terminal after GitHub CI passes.
5
+ Do not configure an `NPM_TOKEN`, trusted publisher, OIDC identity, or automated
6
+ release workflow for npm publication.
7
+
8
+ ## 1. Prepare and verify
9
+
10
+ From the extracted archive:
11
+
12
+ ```bash
13
+ unzip ssrwire.zip # skip when already inside a source checkout
14
+ cd ssrwire
15
+ nvm use 24
16
+ node --version
17
+ npm --version
18
+ npm ci
19
+ npm run check
20
+ npm pack --dry-run
21
+ ```
22
+
23
+ Install and authenticate GitHub CLI if needed:
24
+
25
+ ```bash
26
+ brew install gh
27
+ gh auth login -h github.com --web
28
+ gh auth status -h github.com
29
+ ```
30
+
31
+ ## 2. Create the Git history
32
+
33
+ ```bash
34
+ git init -b main
35
+ git add -- \
36
+ .dockerignore .editorconfig .env.example .github .gitignore \
37
+ CHANGELOG.md CONTRIBUTING.md Dockerfile LICENSE PUBLISHING.md README.md SECURITY.md \
38
+ biome.json examples package.json package-lock.json scripts src test \
39
+ tsconfig.build.json tsconfig.json vitest.config.ts
40
+ git commit -m "Initial SSRWire release"
41
+ ```
42
+
43
+ ## 3. Create and push the public repository
44
+
45
+ ```bash
46
+ gh repo create lame13/ssrwire \
47
+ --public \
48
+ --source=. \
49
+ --remote=origin \
50
+ --push \
51
+ --description "Inspect streamed SSR HTML, metadata timing, and crawler-specific delivery from the command line." \
52
+ --homepage "https://nikom.work"
53
+ ```
54
+
55
+ ## 4. Add repository topics
56
+
57
+ ```bash
58
+ gh repo edit lame13/ssrwire \
59
+ --add-topic technical-seo \
60
+ --add-topic seo \
61
+ --add-topic ssr \
62
+ --add-topic streaming-html \
63
+ --add-topic nextjs \
64
+ --add-topic nuxt \
65
+ --add-topic astro \
66
+ --add-topic crawler \
67
+ --add-topic typescript \
68
+ --add-topic cli \
69
+ --add-topic seo-tools \
70
+ --add-topic github-actions \
71
+ --add-topic sarif
72
+ ```
73
+
74
+ Enable the private vulnerability-reporting channel referenced by
75
+ `SECURITY.md` (this requires repository admin permission):
76
+
77
+ ```bash
78
+ gh api --method PUT repos/lame13/ssrwire/private-vulnerability-reporting
79
+ ```
80
+
81
+ Alternatively, enable **Private vulnerability reporting** in the repository's
82
+ Settings → Security settings before publishing the first release.
83
+
84
+ Recommended repository description:
85
+
86
+ > Inspect streamed SSR HTML, metadata timing, and crawler-specific delivery from the command line.
87
+
88
+ Wait for the repository's `CI` workflow to pass before publishing.
89
+
90
+ ## 5. Publish version 0.1.0 to npm locally
91
+
92
+ Verify the release from a clean checkout before starting npm's short-lived
93
+ authenticated session:
94
+
95
+ ```bash
96
+ npm ci
97
+ npm run check
98
+ npm pack --dry-run
99
+ npm view ssrwire
100
+ ```
101
+
102
+ For the first release, `npm view` returning a 404 means the name is not
103
+ currently published. Start authentication only after verification so the
104
+ session remains fresh for publication:
105
+
106
+ ```bash
107
+ npm login --auth-type=web --registry=https://registry.npmjs.org
108
+ npm whoami --registry=https://registry.npmjs.org
109
+ npm publish --access public --registry=https://registry.npmjs.org
110
+ npm view ssrwire version dist-tags homepage keywords repository.url --json
111
+ npm logout --registry=https://registry.npmjs.org
112
+ ```
113
+
114
+ Run login and publish in a foreground interactive terminal. Complete npm's
115
+ browser, passkey, or two-factor-authentication challenge when prompted. Never
116
+ put an OTP in a command argument, and do not add an `NPM_TOKEN` to this
117
+ repository.
118
+
119
+ If the package is not immediately visible after `npm publish` succeeds, wait
120
+ for npm's publish-time scanning to finish instead of publishing the same
121
+ version again.
122
+
123
+ After npm confirms `0.1.0`, create the matching source release:
124
+
125
+ ```bash
126
+ node scripts/clean.mjs
127
+ rm -rf node_modules/.vite
128
+ git status --short
129
+ git tag -a v0.1.0 -m "SSRWire v0.1.0"
130
+ git push origin v0.1.0
131
+ gh release create v0.1.0 --generate-notes --title "SSRWire v0.1.0"
132
+ ```
133
+
134
+ `git status --short` must print nothing before tagging the release.
package/README.md ADDED
@@ -0,0 +1,403 @@
1
+ # SSRWire
2
+
3
+ [![CI](https://github.com/lame13/ssrwire/actions/workflows/ci.yml/badge.svg)](https://github.com/lame13/ssrwire/actions/workflows/ci.yml)
4
+ [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
5
+
6
+ Inspect streamed SSR HTML, metadata timing, and crawler-specific delivery from
7
+ the command line.
8
+
9
+ SSRWire makes a real HTTP request for each selected user-agent profile, reads
10
+ the response incrementally, and records when important SEO signals become
11
+ observable to its parser. It reports their elapsed time, observed byte
12
+ position, and document location without launching a browser or executing
13
+ JavaScript.
14
+
15
+ ```bash
16
+ npx ssrwire https://example.com/product
17
+ ```
18
+
19
+ ## Why this exists
20
+
21
+ Modern SSR output is not always one complete HTML document delivered at once:
22
+
23
+ - metadata may arrive later than the first meaningful content;
24
+ - a framework may intentionally stream metadata into `<body>` for a capable
25
+ crawler while blocking for an HTML-limited bot;
26
+ - browser, search-crawler, and social-crawler user agents may receive different
27
+ titles, canonicals, robots directives, redirects, or statuses;
28
+ - an interrupted or oversized stream may never deliver the expected elements;
29
+ - a working hydrated page can hide thin or incomplete source HTML.
30
+
31
+ SSRWire turns those behaviors into small, repeatable HTTP-level checks. It is
32
+ not a browser, a JavaScript renderer, a packet capture, or a replacement for a
33
+ site crawler.
34
+
35
+ ## Requirements
36
+
37
+ - Node.js 22.12.0 or newer
38
+ - No browser installation
39
+
40
+ Run it without installing:
41
+
42
+ ```bash
43
+ npx ssrwire https://example.com/
44
+ npx ssrwire https://example.com/ https://example.com/pricing/
45
+ ```
46
+
47
+ Or add it to a project:
48
+
49
+ ```bash
50
+ npm install --save-dev ssrwire
51
+ npx ssrwire init
52
+ npx ssrwire check
53
+ ```
54
+
55
+ ## Quick start
56
+
57
+ Create `ssrwire.config.yml`:
58
+
59
+ ```bash
60
+ npx ssrwire init
61
+ ```
62
+
63
+ Then run all configured targets:
64
+
65
+ ```bash
66
+ npx ssrwire check
67
+ npx ssrwire check --format json --output reports/ssrwire.json
68
+ npx ssrwire check --format sarif --output reports/ssrwire.sarif
69
+ ```
70
+
71
+ One-off checks need no config:
72
+
73
+ ```bash
74
+ npx ssrwire check \
75
+ https://example.com/ \
76
+ https://example.com/pricing/ \
77
+ --agent browser \
78
+ --agent googlebot \
79
+ --fail-on warning
80
+ ```
81
+
82
+ The root command and `check` are equivalent, so `npx ssrwire URL` is the short
83
+ form of `npx ssrwire check URL`.
84
+
85
+ ## What it observes
86
+
87
+ For each target and agent, SSRWire captures:
88
+
89
+ - response status, final URL, redirect chain, and an allowlisted response-header snapshot;
90
+ - time to response headers, first response-body bytes, and completed body;
91
+ - total bytes delivered to the stream parser and a body fingerprint;
92
+ - title, meta description, canonical, meta robots, H1, first main-content
93
+ text, and JSON-LD blocks;
94
+ - elapsed arrival time, observed byte position, and `head`/`body` location for
95
+ each signal;
96
+ - clean completion, timeout, network failure, invalid response, or configured
97
+ byte-limit termination.
98
+
99
+ It then checks:
100
+
101
+ | Contract | Default finding |
102
+ |---|---|
103
+ | Unexpected status or configured final URL | Error |
104
+ | Missing or explicit non-HTML `Content-Type` | Error / incomplete run |
105
+ | Missing title | Error |
106
+ | Missing description, canonical, H1, or main text | Warning |
107
+ | Duplicate or conflicting title, description, canonical, or robots values | Warning |
108
+ | Invalid JSON-LD | Warning |
109
+ | JSON-LD block/count exceeds the bounded analysis budget | Warning |
110
+ | Critical metadata in `<body>` for a profile that requires head metadata | Error |
111
+ | Status, final URL, title, canonical, or robots drift between profiles | Warning |
112
+ | First byte or required-signal arrival over a configured limit | Warning |
113
+ | Timeout, truncation, network error, or another incomplete probe | Error / incomplete run |
114
+
115
+ Body-located metadata is not inherently an error. SSRWire only fails it for an
116
+ agent whose profile declares `requiresHeadMetadata: true`. This matters for
117
+ frameworks such as Next.js that can deliberately stream metadata differently
118
+ for JavaScript-capable and HTML-limited bots.
119
+
120
+ ## Agent profiles
121
+
122
+ The default run uses:
123
+
124
+ | Key | Intended view | Requires metadata in `<head>` |
125
+ |---|---|---:|
126
+ | `browser` | Normal browser user agent | No |
127
+ | `googlebot` | Googlebot user agent | No |
128
+ | `bingbot` | Bingbot user agent | Yes |
129
+ | `twitterbot` | X/Twitter link-preview user agent | Yes |
130
+
131
+ `facebook` is also built in and can be selected explicitly. A repeated CLI
132
+ `--agent` list replaces the configured/default list for that run:
133
+
134
+ ```bash
135
+ npx ssrwire https://example.com/ --agent googlebot --agent facebook
136
+ ```
137
+
138
+ These profiles send user-agent strings; they do not prove how a real crawler
139
+ will fetch, render, index, or cache a page. SSRWire does not perform crawler IP
140
+ or reverse-DNS verification. `requiresHeadMetadata` is SSRWire's default audit
141
+ policy for a profile, not a guarantee about that crawler's current parser or
142
+ rendering capabilities; use a custom profile when your contract differs.
143
+
144
+ Generic `robots` directives apply to every profile. Matching `googlebot` and
145
+ `bingbot` directives are combined with the generic directives, with restrictive
146
+ rules winning. SSRWire keeps audiences separate for duplicate checks and warns
147
+ when a crawler-specific permissive rule cannot relax a generic restriction.
148
+
149
+ Custom profiles are supported in configuration:
150
+
151
+ ```yaml
152
+ agents:
153
+ - browser
154
+ - key: internal-preview-bot
155
+ label: Internal preview bot
156
+ userAgent: ExamplePreviewBot/1.0
157
+ requiresHeadMetadata: true
158
+ ```
159
+
160
+ ## Configuration
161
+
162
+ SSRWire automatically looks for `ssrwire.config.yml`,
163
+ `ssrwire.config.yaml`, or `ssrwire.config.json`. An explicit `--config` path
164
+ takes precedence.
165
+
166
+ ```yaml
167
+ targets:
168
+ - url: https://example.com/
169
+ expectedStatus: 200
170
+ expectedFinalUrl: https://example.com/
171
+ require:
172
+ title: true
173
+ description: true
174
+ canonical: true
175
+ h1: true
176
+ mainText: true
177
+ maxFirstByteMs: 1200
178
+ maxCriticalMs: 2500
179
+
180
+ - url: https://example.com/not-found/
181
+ expectedStatus: [404]
182
+ require:
183
+ title: true
184
+ description: false
185
+ canonical: false
186
+ h1: true
187
+ mainText: true
188
+
189
+ agents:
190
+ - browser
191
+ - googlebot
192
+ - bingbot
193
+ - twitterbot
194
+
195
+ headers:
196
+ x-preview-token: "${PREVIEW_TOKEN}"
197
+
198
+ timeoutMs: 15000
199
+ maxBytes: 10485760
200
+ maxRedirects: 10
201
+ ```
202
+
203
+ A target can also be a plain URL string when defaults are sufficient:
204
+
205
+ ```yaml
206
+ targets:
207
+ - https://example.com/
208
+ - https://example.com/pricing/
209
+ ```
210
+
211
+ Defaults:
212
+
213
+ - expected status: `200`;
214
+ - title, description, canonical, H1, and main text: required;
215
+ - agents: `browser`, `googlebot`, `bingbot`, and `twitterbot`;
216
+ - timeout: 15 seconds per probe;
217
+ - response limit: 10 MiB;
218
+ - redirect limit: 10.
219
+
220
+ Unknown configuration keys are rejected. URLs must be absolute HTTP or HTTPS
221
+ URLs and cannot contain embedded credentials.
222
+
223
+ ### Protected previews
224
+
225
+ Header values can interpolate environment variables. Export them in the
226
+ current shell or provide them through the CI secret store; SSRWire does not
227
+ load `.env` files itself.
228
+
229
+ ```bash
230
+ export PREVIEW_TOKEN="..."
231
+ npx ssrwire check
232
+ ```
233
+
234
+ For an ephemeral override:
235
+
236
+ ```bash
237
+ npx ssrwire https://preview.example.com/ \
238
+ --header "x-preview-token: $PREVIEW_TOKEN"
239
+ ```
240
+
241
+ CLI headers override a configured header with the same case-insensitive name.
242
+ SSRWire rejects `Accept-Encoding`, `Host`, `Content-Length`, `Connection`,
243
+ `Transfer-Encoding`, and `User-Agent` overrides. Custom headers are sent to the
244
+ initial origin and same-origin redirects only; the first cross-origin redirect removes them for
245
+ the rest of that probe. Request-header configuration is not serialized. The
246
+ CLI and `runAudit()` redact known values and common URL/base64 encodings from
247
+ the complete probe, including parsed HTML signals, response-header snapshots,
248
+ redirects, and errors. A target can apply an unknown transformation before
249
+ reflecting a secret, so review reports from untrusted targets before sharing
250
+ them. Direct low-level `probeUrl()` callers should apply `redactProbe()` before
251
+ persisting results.
252
+
253
+ The final response must declare `text/html` or `application/xhtml+xml` as its
254
+ `Content-Type`; parameters such as `charset=utf-8` are allowed. SSRWire does not
255
+ sniff headerless, JSON, text, or binary responses for HTML-looking fragments.
256
+
257
+ To keep hostile or accidentally huge pages bounded, SSRWire retains at most
258
+ 256 signals of each repeated metadata kind, analyzes at most 64 JSON-LD blocks,
259
+ and captures at most 1,048,576 characters from one JSON-LD block. Exceeding a
260
+ JSON-LD analysis budget produces a dedicated warning rather than being
261
+ mislabeled as invalid JSON. The configured response-byte limit remains the
262
+ outer bound.
263
+
264
+ ## Timing interpretation
265
+
266
+ SSRWire reports when its own process observed bytes and parsed elements. That
267
+ is useful for regression testing, but it is not a record of the application's
268
+ original `flush()` calls or network packets.
269
+
270
+ CDNs, reverse proxies, compression, TLS, HTTP implementations, and local
271
+ buffering can split or coalesce data before the process receives it. Agent
272
+ profiles are requested separately, and network/cache variance can affect their
273
+ times. Use timing thresholds with margin and compare repeated CI runs from a
274
+ stable location. Treat byte positions as parser-observation offsets, not
275
+ transfer-size or packet-boundary evidence. They count bytes delivered by the
276
+ Fetch implementation to SSRWire; those bytes are post-content-decoding when a
277
+ server ignores SSRWire's `Accept-Encoding: identity` request.
278
+
279
+ ## CLI reference
280
+
281
+ ```text
282
+ ssrwire [urls...] [options]
283
+ ssrwire check [urls...] [options]
284
+ ssrwire init [path] [--force]
285
+ ```
286
+
287
+ Check options:
288
+
289
+ | Option | Purpose |
290
+ |---|---|
291
+ | `-c, --config <path>` | Use a specific YAML or JSON config |
292
+ | `-a, --agent <name>` | Select a built-in agent; repeatable |
293
+ | `-H, --header "Name: value"` | Add/override a request header; repeatable |
294
+ | `--timeout <ms>` | Override request timeout |
295
+ | `--max-bytes <bytes>` | Override response-body limit |
296
+ | `--max-redirects <count>` | Override redirect limit |
297
+ | `-f, --format <format>` | `terminal`, `json`, or `sarif` |
298
+ | `-o, --output <path>` | Write the report to a file |
299
+ | `--fail-on <level>` | `error`, `warning`, or `never` |
300
+ | `--no-color` | Disable terminal color |
301
+
302
+ Config-file targets and CLI URLs are combined, with exact duplicate URLs
303
+ removed.
304
+
305
+ ## Reports and exit codes
306
+
307
+ - `terminal`: compact tables and findings for local use.
308
+ - `json`: structured machine-readable evidence, including every probe and timing
309
+ signal.
310
+ - `sarif`: findings suitable for GitHub Code Scanning and other SARIF 2.1.0
311
+ consumers.
312
+
313
+ Exit codes are stable:
314
+
315
+ - `0`: the run completed and passed the selected policy;
316
+ - `1`: the run completed but crossed the `--fail-on` threshold;
317
+ - `2`: configuration/setup failure or incomplete probe evidence.
318
+
319
+ `--fail-on never` suppresses policy failures, but it never converts an
320
+ incomplete probe into a pass.
321
+
322
+ ## GitHub Actions
323
+
324
+ Copy [examples/github-actions.yml](examples/github-actions.yml) into the site
325
+ repository and commit [examples/ssrwire.config.yml](examples/ssrwire.config.yml)
326
+ as `ssrwire.config.yml`. Store preview credentials as repository or environment
327
+ secrets. The example deliberately withholds those credentials from pull-request
328
+ runs because the checked-out configuration is controlled by that pull request;
329
+ credentialed checks run only after trusted code reaches the protected branch or
330
+ through a manual dispatch. Keep PR targets public and credential-free. If the
331
+ main audit requires `${PREVIEW_TOKEN}`, point the PR step at a separate
332
+ credential-free configuration or remove the PR trigger.
333
+
334
+ The workflow always keeps the SARIF file as a downloadable artifact. It also
335
+ uploads findings to Code Scanning for public repositories. Private/internal
336
+ repositories can remove the public-only condition after GitHub Code Security is
337
+ enabled for that repository.
338
+
339
+ This repository's own CI tests Node.js 22.12.0 and 24, runs the packaged CLI smoke
340
+ test on macOS and Windows, validates the npm tarball, and builds and executes
341
+ the Docker image. It contains no automatic npm publishing job; npm releases
342
+ are made manually from a local interactive terminal.
343
+
344
+ ## Docker
345
+
346
+ The image is a small, browser-free Node.js runtime and runs as the non-root
347
+ `node` user:
348
+
349
+ ```bash
350
+ docker build -t ssrwire .
351
+ docker run --rm ssrwire https://example.com/
352
+ ```
353
+
354
+ Run a mounted configuration:
355
+
356
+ ```bash
357
+ docker run --rm \
358
+ --env PREVIEW_TOKEN \
359
+ --volume "$PWD/ssrwire.config.yml:/work/ssrwire.config.yml:ro" \
360
+ --workdir /work \
361
+ ssrwire check
362
+ ```
363
+
364
+ ## Programmatic API
365
+
366
+ The package exports the probe, parser, analysis, and reporter primitives used
367
+ by the CLI:
368
+
369
+ ```ts
370
+ import { loadConfig, renderJson, runAudit } from "ssrwire";
371
+
372
+ const config = await loadConfig({ urls: ["https://example.com/"] });
373
+ const audit = await runAudit(config);
374
+ process.stdout.write(renderJson(audit));
375
+ ```
376
+
377
+ Treat the JSON report's top-level `version` as the SSRWire software version,
378
+ not a promise that every nested field will remain unchanged across major
379
+ versions.
380
+
381
+ ## Scope
382
+
383
+ SSRWire does not execute JavaScript, inspect a hydrated DOM, measure Core Web
384
+ Vitals, discover URLs, validate indexing, bypass access controls, or emulate a
385
+ crawler's rendering pipeline. Use [RoutePlay](https://github.com/lame13/routeplay)
386
+ when the question is server HTML versus a cold browser versus real client-side
387
+ navigation.
388
+
389
+ Run SSRWire only against targets you are authorized to inspect. Keep target
390
+ lists intentionally small; one run starts one probe per target/profile pair,
391
+ with additional requests for redirect hops.
392
+
393
+ ## Development
394
+
395
+ ```bash
396
+ npm ci
397
+ npm run check
398
+ ```
399
+
400
+ See [CONTRIBUTING.md](CONTRIBUTING.md), [SECURITY.md](SECURITY.md),
401
+ [CHANGELOG.md](CHANGELOG.md), and [PUBLISHING.md](PUBLISHING.md).
402
+
403
+ MIT licensed. Built by [Niko M.](https://nikom.work).
package/SECURITY.md ADDED
@@ -0,0 +1,26 @@
1
+ # Security policy
2
+
3
+ ## Supported versions
4
+
5
+ Security fixes are applied to the latest released version.
6
+
7
+ ## Reporting a vulnerability
8
+
9
+ Please do not open a public issue for a vulnerability that could expose
10
+ credentials, bypass SSRWire's header protections, or cause unsafe file or
11
+ network behavior. Use GitHub's private vulnerability reporting for
12
+ `lame13/ssrwire` instead:
13
+
14
+ <https://github.com/lame13/ssrwire/security/advisories/new>
15
+
16
+ Include the affected version, reproduction steps, expected impact, and any
17
+ suggested mitigation. Remove real tokens, cookies, and private URLs from the
18
+ report. You should receive an initial response within seven days.
19
+
20
+ ## Operational safety
21
+
22
+ SSRWire sends real HTTP requests to every configured target for every selected
23
+ agent profile. Run it only against systems you are authorized to test. Treat
24
+ custom headers as secrets, prefer environment-variable interpolation, and do
25
+ not commit populated `.env` files or generated reports containing private
26
+ URLs.
@@ -0,0 +1,14 @@
1
+ import type { AgentProfile } from "./types.js";
2
+ export declare const BUILTIN_AGENTS: Readonly<Record<string, AgentProfile>>;
3
+ export type BuiltinAgentKey = "browser" | "googlebot" | "bingbot" | "twitterbot" | "facebook";
4
+ export type AgentInput = string | {
5
+ readonly key: string;
6
+ readonly label?: string;
7
+ readonly userAgent: string;
8
+ readonly requiresHeadMetadata?: boolean;
9
+ };
10
+ /** Resolve a built-in key/alias or validate a complete custom agent profile. */
11
+ export declare function resolveAgent(input: AgentInput): AgentProfile;
12
+ /** Resolve an ordered list and reject duplicate keys, which would make reports ambiguous. */
13
+ export declare function resolveAgents(inputs: readonly AgentInput[]): AgentProfile[];
14
+ //# sourceMappingURL=agents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAgChE,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,YAAY,GAAG,UAAU,CAAC;AAC9F,MAAM,MAAM,UAAU,GAClB,MAAM,GACN;IACE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;CACzC,CAAC;AAqCN,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,YAAY,CAgC5D;AAED,6FAA6F;AAC7F,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,GAAG,YAAY,EAAE,CAW3E"}