pagespeed-quest 0.9.1 → 0.11.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/CHANGELOG.md +11 -0
- package/README.ja.md +42 -0
- package/README.md +43 -0
- package/build/cli.d.ts +8 -0
- package/build/cli.js +264 -0
- package/build/command.js +3 -209
- package/build/llm.d.ts +20 -0
- package/build/llm.js +42 -0
- package/build/recording.d.ts +2 -0
- package/build/recording.js +125 -8
- package/llmdocs/00-guide.md +124 -0
- package/llmdocs/10-inventory.md +133 -0
- package/llmdocs/20-experiments.md +194 -0
- package/llmdocs/30-measuring.md +117 -0
- package/llmdocs/90-commands.md +181 -0
- package/package.json +14 -8
- package/scripts/build-llm-docs.js +70 -0
- package/scripts/validate-plugin-skills.js +117 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Experiments — which edit models which optimization
|
|
2
|
+
|
|
3
|
+
Each recipe is: the hypothesis, the exact edit, what should move, and what to
|
|
4
|
+
watch out for. Make one edit, measure, record the delta, revert or keep. The
|
|
5
|
+
metric names are the Lighthouse ones reported in `artifacts/summary.md`
|
|
6
|
+
(LCP ×0.25, CLS ×0.25, TBT ×0.30, FCP ×0.10, SI ×0.10).
|
|
7
|
+
|
|
8
|
+
Paths below are relative to the inventory directory.
|
|
9
|
+
|
|
10
|
+
## Images
|
|
11
|
+
|
|
12
|
+
### Compress or re-encode an image
|
|
13
|
+
|
|
14
|
+
**Hypothesis** "the hero image is far heavier than it needs to be."
|
|
15
|
+
|
|
16
|
+
Replace the file in place, keeping the same path:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
cwebp -q 75 contents/GET/https/example.com/img/hero.jpg \
|
|
20
|
+
-o contents/GET/https/example.com/img/hero.jpg
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Then, in `index.json`, set the served type to match the new bytes:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{ "contentTypeMime": "image/webp", "rawHeaders": { "content-type": "image/webp" } }
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Moves** LCP, SI, and total transfer. Transfer time falls in proportion to the
|
|
30
|
+
byte count at the recorded `mbps`.
|
|
31
|
+
|
|
32
|
+
**Watch out** The URL still ends in `.jpg`; that is fine, browsers go by
|
|
33
|
+
`Content-Type`. Do not touch `content-length`. If the format is not supported by
|
|
34
|
+
the recorded device profile the image simply fails to decode — check
|
|
35
|
+
`psq capture`.
|
|
36
|
+
|
|
37
|
+
### Serve a correctly sized image
|
|
38
|
+
|
|
39
|
+
Same edit, but resize rather than re-encode. This is usually the larger win on
|
|
40
|
+
mobile recordings, because the recorded page often ships a desktop-sized asset.
|
|
41
|
+
Compare `summary.md`'s image traffic line before and after.
|
|
42
|
+
|
|
43
|
+
### Lazy-load below-the-fold images
|
|
44
|
+
|
|
45
|
+
Edit the document: add `loading="lazy"` to `<img>` elements below the fold.
|
|
46
|
+
|
|
47
|
+
**Moves** FCP and SI, sometimes LCP indirectly (bandwidth freed for the hero).
|
|
48
|
+
Nothing needs to change in `index.json` — the lazily-loaded requests simply do
|
|
49
|
+
not happen during the measured window.
|
|
50
|
+
|
|
51
|
+
### Preload the LCP image
|
|
52
|
+
|
|
53
|
+
```html
|
|
54
|
+
<link rel="preload" as="image" href="/img/hero.webp" fetchpriority="high">
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
in `<head>`. **Moves** LCP. **Watch out** the recorded `ttfbMs` of that resource
|
|
58
|
+
still applies as a fixed delay, so the win is bounded by it.
|
|
59
|
+
|
|
60
|
+
## Scripts
|
|
61
|
+
|
|
62
|
+
### Defer or async a render-blocking script
|
|
63
|
+
|
|
64
|
+
Edit the document, add `defer` (order preserved) or `async` (order not).
|
|
65
|
+
|
|
66
|
+
**Moves** FCP, LCP, SI. TBT often moves too, because the work lands after the
|
|
67
|
+
first paint.
|
|
68
|
+
|
|
69
|
+
### Remove a third-party tag
|
|
70
|
+
|
|
71
|
+
Delete the `<script>` from the document. Optionally also delete the resource
|
|
72
|
+
from `index.json` — but only after the reference is gone, or the browser will
|
|
73
|
+
record a failed request (see the 404 rule).
|
|
74
|
+
|
|
75
|
+
**Moves** TBT most visibly, plus whatever the tag was blocking. This is the
|
|
76
|
+
single most informative experiment on most commercial pages: it puts a number on
|
|
77
|
+
"what is the tag manager costing us".
|
|
78
|
+
|
|
79
|
+
**Watch out** Some tags inject further requests. Removing the parent removes
|
|
80
|
+
those too, which is realistic — but it makes the delta larger than the tag's own
|
|
81
|
+
weight, so say so when reporting.
|
|
82
|
+
|
|
83
|
+
### Shrink the bundle
|
|
84
|
+
|
|
85
|
+
Replace the file under `contents/` with a smaller build (drop a polyfill, remove
|
|
86
|
+
a library, run a real minifier). Because the browser actually executes what you
|
|
87
|
+
serve, **TBT and main-thread time respond for real** — this is not a simulated
|
|
88
|
+
number.
|
|
89
|
+
|
|
90
|
+
## CSS and fonts
|
|
91
|
+
|
|
92
|
+
### Inline critical CSS, defer the rest
|
|
93
|
+
|
|
94
|
+
In the document, add a `<style>` block with the above-the-fold rules and switch
|
|
95
|
+
the stylesheet link to a deferred load:
|
|
96
|
+
|
|
97
|
+
```html
|
|
98
|
+
<link rel="preload" href="/assets/app.css" as="style" onload="this.rel='stylesheet'">
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Moves** FCP, LCP, SI. **Watch out** CLS often gets *worse* if the inlined
|
|
102
|
+
subset is incomplete — check `psq capture --compare` as well as the score.
|
|
103
|
+
|
|
104
|
+
### Remove unused CSS
|
|
105
|
+
|
|
106
|
+
Edit the stylesheet under `contents/`. Remember `minify: true` re-minifies, so
|
|
107
|
+
the win shows up as fewer bytes, not as tidier formatting.
|
|
108
|
+
|
|
109
|
+
### `font-display: swap` and font preloading
|
|
110
|
+
|
|
111
|
+
Edit the `@font-face` block in the stylesheet, and/or add
|
|
112
|
+
`<link rel="preload" as="font" crossorigin>` to the document.
|
|
113
|
+
|
|
114
|
+
**Moves** FCP and LCP when text was the largest element; CLS if the swap causes
|
|
115
|
+
reflow. Subsetting the font file itself is the transfer-size half of the same
|
|
116
|
+
experiment.
|
|
117
|
+
|
|
118
|
+
## Network and server
|
|
119
|
+
|
|
120
|
+
These are the levers that do not exist in a normal development environment, and
|
|
121
|
+
they are the reason for the `index.json` fields.
|
|
122
|
+
|
|
123
|
+
### "What if the server responded faster?"
|
|
124
|
+
|
|
125
|
+
Lower `ttfbMs` on the document resource (often 200–800 ms on a real recording):
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
{ "url": "https://example.com/", "ttfbMs": 80 }
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Moves** everything downstream, roughly one-for-one. Run this early — it tells
|
|
132
|
+
you how much of the budget is back-end and therefore *not* fixable in the
|
|
133
|
+
front-end.
|
|
134
|
+
|
|
135
|
+
### "What if we put it on a CDN / on a faster link?"
|
|
136
|
+
|
|
137
|
+
Raise `mbps` on the resources served from the slow host. To model a slower
|
|
138
|
+
network instead, lower it. Apply it to a whole host, not a single file, or the
|
|
139
|
+
result will not resemble anything real.
|
|
140
|
+
|
|
141
|
+
### "What if we finally enabled compression?"
|
|
142
|
+
|
|
143
|
+
Set `contentEncoding` to `br` (or `gzip`) on the text resources that had
|
|
144
|
+
`identity`. The proxy re-compresses and the transfer time follows.
|
|
145
|
+
|
|
146
|
+
The reverse — setting `identity` on everything — quantifies what compression is
|
|
147
|
+
already buying you, which is a useful number when someone proposes turning it
|
|
148
|
+
off for CPU reasons.
|
|
149
|
+
|
|
150
|
+
### "What if there were fewer requests?"
|
|
151
|
+
|
|
152
|
+
Concatenate two files into one under `contents/`, update the document to
|
|
153
|
+
reference the survivor, and delete the other entry from `index.json`. The win
|
|
154
|
+
here is mostly the removed `ttfbMs`, so it is only worth it where that value is
|
|
155
|
+
large.
|
|
156
|
+
|
|
157
|
+
## Layout stability
|
|
158
|
+
|
|
159
|
+
### Reserve space for images and embeds
|
|
160
|
+
|
|
161
|
+
Add `width` / `height` (or an `aspect-ratio` rule) in the document or the
|
|
162
|
+
stylesheet. **Moves** CLS, which carries the same weight as LCP — a cheap edit
|
|
163
|
+
with a disproportionate score effect.
|
|
164
|
+
|
|
165
|
+
## What this cannot model
|
|
166
|
+
|
|
167
|
+
State these plainly when reporting results.
|
|
168
|
+
|
|
169
|
+
- **Caching across page views.** Every playback is a cold load.
|
|
170
|
+
- **HTTP/2 prioritisation and connection contention.** Each host is served by
|
|
171
|
+
its own local server; the real contention on a shared connection is not
|
|
172
|
+
reproduced.
|
|
173
|
+
- **Server-side changes that alter the HTML.** You can hand-edit the HTML to
|
|
174
|
+
what the server *would* emit, but the edit is your hypothesis, not a
|
|
175
|
+
measurement of it.
|
|
176
|
+
- **Anything after the load.** Interaction, INP, scrolling — a recording is one
|
|
177
|
+
page load.
|
|
178
|
+
- **Variable content.** Personalised or A/B-tested pages are frozen at the
|
|
179
|
+
moment of recording, which is a feature for comparison and a limitation for
|
|
180
|
+
representativeness.
|
|
181
|
+
|
|
182
|
+
## Reporting a result
|
|
183
|
+
|
|
184
|
+
A useful report answers three questions: what was changed, what moved, and
|
|
185
|
+
whether the page still looks right.
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
Hypothesis : defer the tag manager
|
|
189
|
+
Edit : removed <script src="gtm.js"> from contents/GET/https/example.com/index.html
|
|
190
|
+
Before : LCP 3.41s TBT 890ms Overall 42 (artifacts-baseline/summary.md)
|
|
191
|
+
After : LCP 2.78s TBT 210ms Overall 61 (artifacts/summary.md)
|
|
192
|
+
Visual : no diff (artifacts/capture-diff.txt)
|
|
193
|
+
Caveat : removing the parent tag also removed 4 requests it injected
|
|
194
|
+
```
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Measuring — scores, video, screenshots
|
|
2
|
+
|
|
3
|
+
Three measurement surfaces, all driven through the same playback proxy so they
|
|
4
|
+
see the same page. Choose by the question being asked.
|
|
5
|
+
|
|
6
|
+
| Question | Command | Read |
|
|
7
|
+
| --- | --- | --- |
|
|
8
|
+
| Did the score move? | `psq lighthouse playback` | `artifacts/summary.md` |
|
|
9
|
+
| What does the difference look like? | `psq loadshow playback` | `artifacts/loadshow.mp4` |
|
|
10
|
+
| Did I break the page? | `psq capture --compare <png>` | `artifacts/capture-diff.png` |
|
|
11
|
+
|
|
12
|
+
Keep the two sides in separate artifact directories. `-a` is an option of the
|
|
13
|
+
`lighthouse` / `loadshow` group, so it goes before the sub-subcommand.
|
|
14
|
+
|
|
15
|
+
## Lighthouse before / after
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
psq -i ./baseline lighthouse -a ./artifacts-baseline playback
|
|
19
|
+
psq -i ./inventory lighthouse -a ./artifacts-after playback
|
|
20
|
+
diff artifacts-baseline/summary.md artifacts-after/summary.md
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Each run writes:
|
|
24
|
+
|
|
25
|
+
| File | Contents |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| `summary.md` | metrics, per-metric scores with weights, overall score, traffic and resource size by type |
|
|
28
|
+
| `lighthouse.report.html` | the full report (open it when a number needs explaining) |
|
|
29
|
+
| `lighthouse.report.json` | the same, machine-readable |
|
|
30
|
+
| `lighthouse.digest.png` | score gauge and metrics as one image — the thing to paste into a report |
|
|
31
|
+
| `lighthouse-0.trace.json`, `*.devtoolslog.json` | saved assets (`--save-assets`) |
|
|
32
|
+
|
|
33
|
+
`summary.md` is the file to diff. It is deliberately short and stable, so the
|
|
34
|
+
diff shows the delta and nothing else.
|
|
35
|
+
|
|
36
|
+
Options on the `lighthouse` group: `-a/--artifacts` (default `./artifacts`),
|
|
37
|
+
`-t/--timeout` (ms, default 30000), `-l/--laud` — show the browser window and
|
|
38
|
+
open the report at the end. `--laud` is for watching a run, not for measuring;
|
|
39
|
+
a visible browser is not the same environment as the headless one.
|
|
40
|
+
|
|
41
|
+
**Repeat before concluding.** Lighthouse noise on a fixed recording is a few
|
|
42
|
+
points. Three runs per side is usually enough to see whether a delta is real.
|
|
43
|
+
|
|
44
|
+
## Video of the load, before and after
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
psq -i ./baseline loadshow -a ./artifacts-baseline playback
|
|
48
|
+
psq -i ./inventory loadshow -a ./artifacts-after playback
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Each writes `<artifacts>/loadshow.mp4` — a filmstrip of the load, laid out in
|
|
52
|
+
3 columns for mobile recordings and 2 for desktop, plus
|
|
53
|
+
`<artifacts>/loadshow/summary.md` and the intermediate frames in
|
|
54
|
+
`<artifacts>/loadshow/`.
|
|
55
|
+
|
|
56
|
+
Use `-c/--credit` to stamp a label into the video, which is what makes a pair of
|
|
57
|
+
videos legible side by side:
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
psq -i ./baseline loadshow -a ./artifacts-baseline -c "Before" playback
|
|
61
|
+
psq -i ./inventory loadshow -a ./artifacts-after -c "After — deferred GTM" playback
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
To place them side by side, stack the two MP4s with ffmpeg; `psq` does not
|
|
65
|
+
compose videos:
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
ffmpeg -i artifacts-baseline/loadshow.mp4 -i artifacts-after/loadshow.mp4 \
|
|
69
|
+
-filter_complex hstack -c:v libx264 before-after.mp4
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Recording *with* loadshow rather than Lighthouse
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
psq loadshow recording https://example.com/
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Lighthouse and loadshow drive the browser slightly differently. If the goal of
|
|
79
|
+
the quest is the video rather than the score, record with `loadshow recording`
|
|
80
|
+
so that the recording and the playback are driven the same way.
|
|
81
|
+
|
|
82
|
+
## Screenshot and visual diff
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
psq -i ./baseline capture -a ./artifacts-baseline # baseline.png
|
|
86
|
+
psq capture --compare ./artifacts-baseline/capture.png \
|
|
87
|
+
--baseline-label "Before" --current-label "After"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`capture` runs the proxy in **full-throttle** mode — no TTFB, no bandwidth
|
|
91
|
+
simulation — because the question is what the page looks like when fully
|
|
92
|
+
loaded, not when. Never quote a timing number from a `capture` run.
|
|
93
|
+
|
|
94
|
+
Output: `capture.png` (rendered at 400×1600, resized to 200×800),
|
|
95
|
+
`capture-diff.png`, and `capture-diff.txt` with a text digest of the difference.
|
|
96
|
+
|
|
97
|
+
Run this after **every** structural edit. A deleted script that also removed a
|
|
98
|
+
banner will make every metric better and the page wrong.
|
|
99
|
+
|
|
100
|
+
## Reading `summary.md`
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
## Metrics
|
|
104
|
+
- LCP 2780 ms
|
|
105
|
+
- CLS 0.02
|
|
106
|
+
...
|
|
107
|
+
## Scores
|
|
108
|
+
- LCP 0.71 x0.25
|
|
109
|
+
- TBT 0.88 x0.30
|
|
110
|
+
- **Overall** 0.61
|
|
111
|
+
## Traffic: Type / Count / Size / Bytes
|
|
112
|
+
- script / 14 / 512.3 KB / 524595
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
*Traffic* is what crossed the wire (after compression); *Resource* is the
|
|
116
|
+
decompressed size. A change that moves resource size but not traffic size means
|
|
117
|
+
you edited something that compresses away — expect little timing effect.
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Command catalog
|
|
2
|
+
|
|
3
|
+
<!-- Generated by scripts/build-llm-docs.js. Do not edit by hand. -->
|
|
4
|
+
|
|
5
|
+
Options belong to the command they are declared on, so they must appear
|
|
6
|
+
before the next subcommand: `psq -i <dir> lighthouse -a <dir> playback`.
|
|
7
|
+
|
|
8
|
+
## psq
|
|
9
|
+
|
|
10
|
+
Record a web page through a proxy, edit the recording, and replay it to measure the difference
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
Usage: psq [options] [command]
|
|
14
|
+
|
|
15
|
+
Record a web page through a proxy, edit the recording, and replay it to measure the difference
|
|
16
|
+
|
|
17
|
+
Options:
|
|
18
|
+
-v, --version Output the version number
|
|
19
|
+
-i, --inventory <dir> Inventory directory (default: "./inventory")
|
|
20
|
+
-h, --help display help for command
|
|
21
|
+
|
|
22
|
+
Commands:
|
|
23
|
+
lighthouse [options] Run Lighthouse (performance category) via a proxy
|
|
24
|
+
loadshow [options] Run loadshow via a proxy
|
|
25
|
+
proxy [options] Run the playback (or recording) proxy standalone
|
|
26
|
+
capture [options] Capture a screenshot via playback proxy and optionally compare with a baseline
|
|
27
|
+
llm [options] Print the embedded reference for AI agents
|
|
28
|
+
help [command] display help for command
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### psq lighthouse
|
|
32
|
+
|
|
33
|
+
Run Lighthouse (performance category) via a proxy
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
Usage: psq lighthouse [options] [command]
|
|
37
|
+
|
|
38
|
+
Run Lighthouse (performance category) via a proxy
|
|
39
|
+
|
|
40
|
+
Options:
|
|
41
|
+
-a, --artifacts <dir> Artifacts directory (default: "./artifacts")
|
|
42
|
+
-l, --laud Show browser and open the report after completion (default: false)
|
|
43
|
+
-t, --timeout <ms> Timeout milliseconds (default: "30000")
|
|
44
|
+
-h, --help display help for command
|
|
45
|
+
|
|
46
|
+
Commands:
|
|
47
|
+
recording [options] <url> Record contents by lighthouse
|
|
48
|
+
playback Playback contents for lighthouse
|
|
49
|
+
help [command] display help for command
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
#### psq lighthouse recording
|
|
53
|
+
|
|
54
|
+
Record contents by lighthouse
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
Usage: psq lighthouse recording [options] <url>
|
|
58
|
+
|
|
59
|
+
Record contents by lighthouse
|
|
60
|
+
|
|
61
|
+
Arguments:
|
|
62
|
+
url Url to measure performance
|
|
63
|
+
|
|
64
|
+
Options:
|
|
65
|
+
-d, --device <mobile|desktop> Device type (default: "mobile")
|
|
66
|
+
-x, --exclude <pattern> Regex patterns to exclude URLs from recording (repeatable) (default: [])
|
|
67
|
+
-h, --help display help for command
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
#### psq lighthouse playback
|
|
71
|
+
|
|
72
|
+
Playback contents for lighthouse
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
Usage: psq lighthouse playback [options]
|
|
76
|
+
|
|
77
|
+
Playback contents for lighthouse
|
|
78
|
+
|
|
79
|
+
Options:
|
|
80
|
+
-h, --help display help for command
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### psq loadshow
|
|
84
|
+
|
|
85
|
+
Run loadshow via a proxy
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
Usage: psq loadshow [options] [command]
|
|
89
|
+
|
|
90
|
+
Run loadshow via a proxy
|
|
91
|
+
|
|
92
|
+
Options:
|
|
93
|
+
-a, --artifacts <dir> Artifacts directory (default: "./artifacts")
|
|
94
|
+
-c, --credit <string> Credit string
|
|
95
|
+
-t, --timeout <ms> Timeout milliseconds (default: "30000")
|
|
96
|
+
-h, --help display help for command
|
|
97
|
+
|
|
98
|
+
Commands:
|
|
99
|
+
recording [options] <url> Record contents by loadshow
|
|
100
|
+
playback Playback contents for loadshow
|
|
101
|
+
help [command] display help for command
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
#### psq loadshow recording
|
|
105
|
+
|
|
106
|
+
Record contents by loadshow
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
Usage: psq loadshow recording [options] <url>
|
|
110
|
+
|
|
111
|
+
Record contents by loadshow
|
|
112
|
+
|
|
113
|
+
Arguments:
|
|
114
|
+
url Url to measure performance
|
|
115
|
+
|
|
116
|
+
Options:
|
|
117
|
+
-d, --device <mobile|desktop> Device type (default: "mobile")
|
|
118
|
+
-x, --exclude <pattern> Regex patterns to exclude URLs from recording (repeatable) (default: [])
|
|
119
|
+
-h, --help display help for command
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### psq loadshow playback
|
|
123
|
+
|
|
124
|
+
Playback contents for loadshow
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
Usage: psq loadshow playback [options]
|
|
128
|
+
|
|
129
|
+
Playback contents for loadshow
|
|
130
|
+
|
|
131
|
+
Options:
|
|
132
|
+
-h, --help display help for command
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### psq proxy
|
|
136
|
+
|
|
137
|
+
Run the playback (or recording) proxy standalone
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
Usage: psq proxy [options]
|
|
141
|
+
|
|
142
|
+
Run the playback (or recording) proxy standalone
|
|
143
|
+
|
|
144
|
+
Options:
|
|
145
|
+
-p, --port <number> Proxy port (default: "8080")
|
|
146
|
+
-r, --record <url> Recording URL to start the proxy as recording mode (default: "")
|
|
147
|
+
-x, --exclude <pattern> Regex patterns to exclude URLs from recording (repeatable) (default: [])
|
|
148
|
+
-h, --help display help for command
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### psq capture
|
|
152
|
+
|
|
153
|
+
Capture a screenshot via playback proxy and optionally compare with a baseline
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
Usage: psq capture [options]
|
|
157
|
+
|
|
158
|
+
Capture a screenshot via playback proxy and optionally compare with a baseline
|
|
159
|
+
|
|
160
|
+
Options:
|
|
161
|
+
-a, --artifacts <dir> Artifacts directory (default: "./artifacts")
|
|
162
|
+
-t, --timeout <ms> Timeout milliseconds (default: "30000")
|
|
163
|
+
--compare <file> Baseline PNG file to compare against
|
|
164
|
+
--baseline-label <label> Label for the baseline image (default: "前回")
|
|
165
|
+
--current-label <label> Label for the current image (default: "今回")
|
|
166
|
+
-h, --help display help for command
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### psq llm
|
|
170
|
+
|
|
171
|
+
Print the embedded reference for AI agents
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
Usage: psq llm [options]
|
|
175
|
+
|
|
176
|
+
Print the embedded reference for AI agents
|
|
177
|
+
|
|
178
|
+
Options:
|
|
179
|
+
-f, --format <markdown|json> Output format (default: "markdown")
|
|
180
|
+
-h, --help display help for command
|
|
181
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pagespeed-quest",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A framework for efficient web front-end speed improvement",
|
|
6
6
|
"main": "build/index.js",
|
|
@@ -17,16 +17,20 @@
|
|
|
17
17
|
"psq": "build/command.js"
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
|
-
"build": "run-
|
|
20
|
+
"build": "run-s build:module build:llmdocs",
|
|
21
21
|
"build:module": "tsc -p tsconfig.json",
|
|
22
|
+
"build:llmdocs": "node scripts/build-llm-docs.js",
|
|
23
|
+
"validate-plugin-skills": "node scripts/validate-plugin-skills.js",
|
|
22
24
|
"fix": "run-s fix:*",
|
|
23
25
|
"fix:prettier": "prettier \"src/**/*.ts\" --write",
|
|
24
26
|
"fix:lint": "eslint src --ext .ts --fix",
|
|
25
|
-
"test": "run-s build test:*",
|
|
27
|
+
"test": "run-s build:module test:*",
|
|
26
28
|
"test:lint": "eslint src --ext .ts",
|
|
27
29
|
"test:prettier": "prettier \"src/**/*.ts\" --list-different",
|
|
28
30
|
"test:spelling": "cspell \"{README.md,.github/*.md,src/**/*.ts}\"",
|
|
29
31
|
"test:unit": "nyc --silent ava",
|
|
32
|
+
"test:plugins": "node scripts/validate-plugin-skills.js",
|
|
33
|
+
"test:llmdocs": "node scripts/build-llm-docs.js --check",
|
|
30
34
|
"check-cli": "run-s test diff-integration-tests check-integration-tests",
|
|
31
35
|
"check-integration-tests": "run-s check-integration-test:*",
|
|
32
36
|
"diff-integration-tests": "mkdir -p diff && rm -rf diff/test && cp -r test diff/test && rm -rf diff/test/test-*/.git && cd diff && git init --quiet && git add -A && git commit --quiet --no-verify --allow-empty -m 'WIP' && echo '\\n\\nCommitted most recent integration test output in the \"diff\" directory. Review the changes with \"cd diff && git diff HEAD\" or your preferred git diff viewer.'",
|
|
@@ -47,28 +51,29 @@
|
|
|
47
51
|
"adhoc": "yarn build && node build/adhoc.js",
|
|
48
52
|
"command": "yarn build && node build/command.js",
|
|
49
53
|
"prepublishOnly": "yarn build",
|
|
50
|
-
"postinstall": "node scripts/postinstall.js"
|
|
54
|
+
"postinstall": "node scripts/postinstall.js",
|
|
55
|
+
"ai:regen": "run-s build:module build:llmdocs validate-plugin-skills"
|
|
51
56
|
},
|
|
52
57
|
"engines": {
|
|
53
|
-
"node": ">=
|
|
58
|
+
"node": ">=22.19"
|
|
54
59
|
},
|
|
55
60
|
"dependencies": {
|
|
56
61
|
"commander": "^11.1.0",
|
|
57
62
|
"execa": "^9.3.1",
|
|
58
63
|
"iconv-lite": "^0.6.3",
|
|
59
64
|
"jschardet": "^3.0.0",
|
|
60
|
-
"lighthouse": "^
|
|
65
|
+
"lighthouse": "^13.0.3",
|
|
61
66
|
"node-html-to-image": "^5.0.0",
|
|
62
67
|
"node-watch": "^0.7.4",
|
|
63
68
|
"pino": "^9.4.0",
|
|
64
69
|
"pino-pretty": "^11.2.2",
|
|
65
70
|
"prettier": "^2.1.1",
|
|
66
71
|
"puppeteer": "^24.31.0",
|
|
67
|
-
"rust-http-playback-proxy": "
|
|
72
|
+
"rust-http-playback-proxy": "0.9.0",
|
|
68
73
|
"tmp-promise": "^3.0.3"
|
|
69
74
|
},
|
|
70
75
|
"devDependencies": {
|
|
71
|
-
"@ava/typescript": "^
|
|
76
|
+
"@ava/typescript": "^7.0.0",
|
|
72
77
|
"@istanbuljs/nyc-config-typescript": "^1.0.1",
|
|
73
78
|
"@types/node": "^20.8.9",
|
|
74
79
|
"@typescript-eslint/eslint-plugin": "^4.0.1",
|
|
@@ -94,6 +99,7 @@
|
|
|
94
99
|
"files": [
|
|
95
100
|
"build",
|
|
96
101
|
"scripts",
|
|
102
|
+
"llmdocs",
|
|
97
103
|
"!**/*.spec.*",
|
|
98
104
|
"!**/*.json",
|
|
99
105
|
"CHANGELOG.md",
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Generate llmdocs/90-commands.md from the actual `psq` command tree.
|
|
4
|
+
*
|
|
5
|
+
* Run through `yarn build` (and `yarn ai:regen`) after `tsc`, because it walks
|
|
6
|
+
* the compiled command definitions rather than a hand-written list. Never edit
|
|
7
|
+
* llmdocs/90-commands.md by hand — change the command definitions in
|
|
8
|
+
* src/cli.ts and run this again.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFile, writeFile } from 'fs/promises'
|
|
12
|
+
import { dirname, join } from 'path'
|
|
13
|
+
import { fileURLToPath } from 'url'
|
|
14
|
+
|
|
15
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
16
|
+
const projectRoot = join(__dirname, '..')
|
|
17
|
+
const outputPath = join(projectRoot, 'llmdocs', '90-commands.md')
|
|
18
|
+
|
|
19
|
+
const { createProgram } = await import(join(projectRoot, 'build', 'cli.js'))
|
|
20
|
+
|
|
21
|
+
function walk(command, path, depth, lines) {
|
|
22
|
+
const heading = '#'.repeat(Math.min(depth + 1, 6))
|
|
23
|
+
const title = [...path, command.name()].join(' ')
|
|
24
|
+
|
|
25
|
+
lines.push(`${heading} ${title}`)
|
|
26
|
+
lines.push('')
|
|
27
|
+
const description = command.description()
|
|
28
|
+
if (description) {
|
|
29
|
+
lines.push(description)
|
|
30
|
+
lines.push('')
|
|
31
|
+
}
|
|
32
|
+
lines.push('```')
|
|
33
|
+
lines.push(command.helpInformation().trimEnd())
|
|
34
|
+
lines.push('```')
|
|
35
|
+
lines.push('')
|
|
36
|
+
|
|
37
|
+
for (const child of command.commands) {
|
|
38
|
+
walk(child, [...path, command.name()], depth + 1, lines)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const program = createProgram()
|
|
43
|
+
|
|
44
|
+
const lines = [
|
|
45
|
+
'# Command catalog',
|
|
46
|
+
'',
|
|
47
|
+
'<!-- Generated by scripts/build-llm-docs.js. Do not edit by hand. -->',
|
|
48
|
+
'',
|
|
49
|
+
'Options belong to the command they are declared on, so they must appear',
|
|
50
|
+
'before the next subcommand: `psq -i <dir> lighthouse -a <dir> playback`.',
|
|
51
|
+
'',
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
walk(program, [], 1, lines)
|
|
55
|
+
|
|
56
|
+
const generated = `${lines.join('\n').replace(/\n{3,}/g, '\n\n').trimEnd()}\n`
|
|
57
|
+
|
|
58
|
+
if (process.argv.includes('--check')) {
|
|
59
|
+
const current = await readFile(outputPath, 'utf8').catch(() => '')
|
|
60
|
+
if (current !== generated) {
|
|
61
|
+
process.stderr.write(
|
|
62
|
+
`llmdocs/90-commands.md is out of date with the command definitions.\nRun \`yarn ai:regen\` and commit the result — do not edit it by hand.\n`
|
|
63
|
+
)
|
|
64
|
+
process.exit(1)
|
|
65
|
+
}
|
|
66
|
+
process.stdout.write('llmdocs/90-commands.md is up to date\n')
|
|
67
|
+
} else {
|
|
68
|
+
await writeFile(outputPath, generated)
|
|
69
|
+
process.stdout.write(`Wrote ${outputPath}\n`)
|
|
70
|
+
}
|