@progen-labs/spectra 0.1.3 → 0.3.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/README.md +169 -4
- package/bin/spectra.js +126 -11
- package/dist/assets/{index-COnXcwA_.js → index-DY8VmG9y.js} +1 -1
- package/dist/assets/index-Dx1YSFIx.css +1 -0
- package/dist/index.html +2 -2
- package/package.json +17 -8
- package/schema/config.schema.json +94 -0
- package/schema/result.schema.json +749 -0
- package/server/args.js +29 -0
- package/server/cli.js +143 -67
- package/server/compare-cli.js +126 -0
- package/server/explore/evaluate-cli.js +72 -40
- package/server/mcp.js +678 -0
- package/server/output.js +50 -0
- package/server/project-config.js +30 -0
- package/server/recorder.js +4 -1
- package/server/reports-cli.js +31 -0
- package/server/test-mcp.js +170 -0
- package/server/test-result.js +183 -0
- package/server/testing/fixtures.js +49 -0
- package/shared/compare.js +1 -0
- package/shared/result.js +291 -0
- package/src/styles.css +20 -3
- package/dist/assets/index-DxxJOPn9.css +0 -1
package/README.md
CHANGED
|
@@ -14,6 +14,9 @@ and per-component render/change-detection time; source files too, where the fram
|
|
|
14
14
|
functions. A [Progen Labs](https://github.com/progen-labs) project — open source, non-profit, no
|
|
15
15
|
telemetry, no account required.
|
|
16
16
|
|
|
17
|
+
**Documentation:** the full guide, with annotated screenshots of every screen, is at
|
|
18
|
+
[progen-labs.github.io/website](https://progen-labs.github.io/website/#spectra/docs/introduction).
|
|
19
|
+
|
|
17
20
|
## Install
|
|
18
21
|
|
|
19
22
|
```bash
|
|
@@ -52,15 +55,24 @@ Then open the visual dashboard — ranked culprits, React re-renders, long tasks
|
|
|
52
55
|
**PDF export** and **commit-to-commit compare**:
|
|
53
56
|
|
|
54
57
|
```bash
|
|
55
|
-
npx spectra dashboard
|
|
58
|
+
npx spectra dashboard --open
|
|
56
59
|
```
|
|
57
60
|
|
|
61
|
+

|
|
62
|
+
|
|
63
|
+
A [tour of the dashboard](https://progen-labs.github.io/website/#spectra/docs/dashboard), with every part
|
|
64
|
+
labelled, is in the docs.
|
|
65
|
+
|
|
58
66
|
## Commands
|
|
59
67
|
|
|
60
68
|
```
|
|
61
|
-
spectra performance [options]
|
|
62
|
-
spectra
|
|
63
|
-
spectra
|
|
69
|
+
spectra performance [options] Golden-path evaluation (default)
|
|
70
|
+
spectra performance --quick One page-load profile (about 15 s)
|
|
71
|
+
spectra compare <before> <after> Did it get faster? Noise-aware before/after
|
|
72
|
+
spectra reports List saved reports
|
|
73
|
+
spectra mcp [--project <dir>] MCP server for AI tools (stdio)
|
|
74
|
+
spectra dashboard [--port N] [--open] Open the dashboard UI
|
|
75
|
+
spectra --help Full option list
|
|
64
76
|
spectra --version
|
|
65
77
|
```
|
|
66
78
|
|
|
@@ -74,7 +86,147 @@ spectra --version
|
|
|
74
86
|
| `--boot-timeout <s>` | How long to wait for the dev server (default 60) |
|
|
75
87
|
| `--headed` | Show the browser window |
|
|
76
88
|
| `--sign-in <url>` | Sign-in URL template, e.g. `http://host/login/?redirect={url}` |
|
|
89
|
+
| `--quick` | One page-load profile instead of the golden-path suite: the fast loop |
|
|
90
|
+
| `--runs <n>` | Record n times and report the median (with `--quick` or `--mode`) |
|
|
77
91
|
| `--mode live\|load` | Classic single-page profile instead of the golden-path suite |
|
|
92
|
+
| `--json` | Print a machine-readable result on stdout (tables go to stderr) |
|
|
93
|
+
| `--out <file>` | Also write that result to a file (an input for `compare`) |
|
|
94
|
+
| `--config <file>` | Budgets file (default: `spectra.config.json` in your project) |
|
|
95
|
+
|
|
96
|
+
## Measure, change, verify
|
|
97
|
+
|
|
98
|
+
Performance numbers are noisy, so Spectra never trusts a single recording when you ask it whether a change
|
|
99
|
+
helped. Record a few runs before and after, then compare:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npx spectra performance --quick --runs 5 --out before.json
|
|
103
|
+
# …make your change…
|
|
104
|
+
npx spectra performance --quick --runs 5 --out after.json
|
|
105
|
+
npx spectra compare before.json after.json
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
Spectra compare — IMPROVED (confidence: medium)
|
|
110
|
+
✓ JavaScript CPU time: 1.52 s → 92.1 ms (−94%)
|
|
111
|
+
✓ Row (src/Row.jsx) — render time: 1.44 s → 7.4 ms (−99%)
|
|
112
|
+
|
|
113
|
+
Findings: 3 resolved · 2 new · 10 still present
|
|
114
|
+
resolved [critical] Row is slow to render: 1444ms total, 0.2ms per render (7200×)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Each side is reduced to its median, and a difference only counts when it is larger than the run-to-run
|
|
118
|
+
spread. A side can be a `--out` result file, a report id from `spectra reports`, a report file, or several
|
|
119
|
+
ids joined by commas.
|
|
120
|
+
|
|
121
|
+
## Budgets and CI
|
|
122
|
+
|
|
123
|
+
Put limits in `spectra.config.json` at your project root:
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"$schema": "./node_modules/@progen-labs/spectra/schema/config.schema.json",
|
|
128
|
+
"budgets": { "tbtMs": 200, "lcpMs": 2500, "cls": 0.1, "criticalFindings": 0 }
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Every `spectra performance` run checks them: the median for `--quick`/`--mode`, every journey for the
|
|
133
|
+
golden-path suite. Metrics a recording did not measure are skipped, never failed. Available budgets:
|
|
134
|
+
`tbtMs`, `lcpMs`, `fcpMs`, `cls`, `inpMs`, `ttfbMs`, `longTaskCount`, `maxLongTaskMs`, `scriptingMs`,
|
|
135
|
+
`forcedLayoutCount`, `domNodes`, `heapUsedMb`, `transferKb`, `criticalFindings`, `findingCount`.
|
|
136
|
+
|
|
137
|
+
Exit codes make it a CI gate:
|
|
138
|
+
|
|
139
|
+
| Code | Meaning |
|
|
140
|
+
| --- | --- |
|
|
141
|
+
| `0` | Done, and within budget |
|
|
142
|
+
| `1` | Could not run (no Chrome, server down, bad config) |
|
|
143
|
+
| `2` | Ran, but a budget was exceeded, or `compare --fail-on-regression` found a regression |
|
|
144
|
+
|
|
145
|
+
### GitHub Action
|
|
146
|
+
|
|
147
|
+
The repository is also a GitHub Action. On every pull request it profiles the change and its base commit on the
|
|
148
|
+
same runner, compares them beyond run-to-run noise, enforces your budgets, and writes the result to the job summary
|
|
149
|
+
and (optionally) a PR comment:
|
|
150
|
+
|
|
151
|
+
```yaml
|
|
152
|
+
name: Performance
|
|
153
|
+
on: pull_request
|
|
154
|
+
permissions:
|
|
155
|
+
contents: read
|
|
156
|
+
pull-requests: write # only for comment: true
|
|
157
|
+
jobs:
|
|
158
|
+
spectra:
|
|
159
|
+
runs-on: ubuntu-latest
|
|
160
|
+
steps:
|
|
161
|
+
- uses: actions/checkout@v4
|
|
162
|
+
- uses: actions/setup-node@v4
|
|
163
|
+
with: { node-version: 22 }
|
|
164
|
+
- run: npm ci
|
|
165
|
+
- uses: progen-labs/spectra@v1
|
|
166
|
+
with:
|
|
167
|
+
compare-to-base: true
|
|
168
|
+
fail-on-regression: true
|
|
169
|
+
comment: true
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Inputs include `url`, `command`, `mode` (`quick` or `golden-paths`), `runs`, `config`, `fail-on-budget` and
|
|
173
|
+
`working-directory`; outputs are `result-file`, `verdict`, `over-budget` and `summary-file`. Full reports are
|
|
174
|
+
uploaded as an artifact. See the [Action guide](https://progen-labs.github.io/website/#spectra/docs/github-action).
|
|
175
|
+
|
|
176
|
+
## Machine-readable output
|
|
177
|
+
|
|
178
|
+
`--json` prints a small, versioned result on stdout and sends every human-readable line to stderr, so it can
|
|
179
|
+
be piped into `jq`, an editor or an AI agent. Each result has `schemaVersion` and a `kind` (`profile`,
|
|
180
|
+
`evaluation`, `compare` or `reports`) and points at the full reports on disk rather than embedding them.
|
|
181
|
+
Findings carry the file, line, cost in ms and a suggested fix. The format is described by a JSON Schema
|
|
182
|
+
shipped with the package (`schema/result.schema.json`); additive changes keep `schemaVersion`, breaking
|
|
183
|
+
changes bump it. Saved reports carry their own `schemaVersion` too.
|
|
184
|
+
|
|
185
|
+
## Use it from your AI assistant
|
|
186
|
+
|
|
187
|
+
Spectra is an [MCP](https://modelcontextprotocol.io) server too, so an AI coding assistant can profile a page,
|
|
188
|
+
fix what it finds, and prove each fix with before/after measurements.
|
|
189
|
+
|
|
190
|
+
**Claude Code**, with the plugin (adds an *improve-performance* skill and `/spectra:audit`):
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
/plugin marketplace add progen-labs/spectra
|
|
194
|
+
/plugin install spectra@progen-labs
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Any MCP client** (Claude Code without the plugin, Claude Desktop, Cursor, VS Code, Windsurf…): run
|
|
198
|
+
`npx -y @progen-labs/spectra mcp` as a stdio server (Spectra 0.3.0 or later). For example:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
claude mcp add spectra -- npx -y @progen-labs/spectra mcp
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
```json
|
|
205
|
+
{ "mcpServers": { "spectra": { "command": "npx", "args": ["-y", "@progen-labs/spectra", "mcp", "--project", "/path/to/your/app"] } } }
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Tools: `profile_page`, `verify_change` (re-profile and compare against a baseline), `compare_profiles`,
|
|
209
|
+
`run_golden_paths`, `list_reports` and `get_report`. The server profiles pages on this machine only (start it with
|
|
210
|
+
`--allow-remote` to lift that), runs one browser at a time, and stops Chrome and any dev server it started when a
|
|
211
|
+
call is cancelled. See the [MCP guide](https://progen-labs.github.io/website/#spectra/docs/mcp-server).
|
|
212
|
+
|
|
213
|
+
## In VS Code
|
|
214
|
+
|
|
215
|
+
The Spectra extension (`vscode/`) puts results where you work: every finding in the **Problems panel** at the file
|
|
216
|
+
and line responsible, **CodeLens** above the costliest components and functions
|
|
217
|
+
(`⚡ Spectra: Row · 7,200 renders · 1.44 s rendering`), headline numbers in the **status bar**, **Verify against
|
|
218
|
+
baseline** after a change, golden paths, and the dashboard inside the editor. Results that the CLI or an AI
|
|
219
|
+
assistant writes to `.spectra/results` appear automatically, and on VS Code 1.101+ it offers Spectra's MCP server to
|
|
220
|
+
agent mode. It runs your project's Spectra (0.2.0 or later) or `npx`.
|
|
221
|
+
|
|
222
|
+
Until it is listed on the VS Code Marketplace and Open VSX, build and install it from this repository:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
cd vscode && npx @vscode/vsce package --no-dependencies
|
|
226
|
+
code --install-extension spectra-0.1.0.vsix
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
See the [VS Code guide](https://progen-labs.github.io/website/#spectra/docs/vscode).
|
|
78
230
|
|
|
79
231
|
## Safe by default
|
|
80
232
|
|
|
@@ -97,6 +249,19 @@ that cost the most main-thread time. See the
|
|
|
97
249
|
[deep-dive docs](https://github.com/progen-labs/spectra/blob/main/DOCS.md) for how capture, correlation
|
|
98
250
|
and report generation work.
|
|
99
251
|
|
|
252
|
+
## Documentation
|
|
253
|
+
|
|
254
|
+
- **[The guide](https://progen-labs.github.io/website/#spectra/docs/introduction)**: every feature, with
|
|
255
|
+
annotated screenshots. Start with
|
|
256
|
+
[Quick start](https://progen-labs.github.io/website/#spectra/docs/quick-start),
|
|
257
|
+
[Compare before and after](https://progen-labs.github.io/website/#spectra/docs/compare),
|
|
258
|
+
[Budgets and CI](https://progen-labs.github.io/website/#spectra/docs/budgets),
|
|
259
|
+
[JSON output](https://progen-labs.github.io/website/#spectra/docs/json-output),
|
|
260
|
+
[Claude Code](https://progen-labs.github.io/website/#spectra/docs/claude-code), the
|
|
261
|
+
[Findings reference](https://progen-labs.github.io/website/#spectra/docs/findings) and
|
|
262
|
+
[Troubleshooting](https://progen-labs.github.io/website/#spectra/docs/troubleshooting).
|
|
263
|
+
- **[DOCS.md](DOCS.md)**: the in-repo manual, the technical deep dive, and how to contribute.
|
|
264
|
+
|
|
100
265
|
## License
|
|
101
266
|
|
|
102
267
|
MIT © Progen Labs
|
package/bin/spectra.js
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
* Spectra CLI — a Progen Labs project.
|
|
4
4
|
*
|
|
5
5
|
* spectra performance [--url <page>] [profiler options] profile a frontend
|
|
6
|
+
* spectra compare <before> <after> before vs after, noise-aware
|
|
7
|
+
* spectra reports list saved reports
|
|
8
|
+
* spectra mcp [--project <dir>] MCP server for AI tools (stdio)
|
|
6
9
|
* spectra dashboard open the dashboard UI
|
|
7
10
|
* spectra --help show this help
|
|
8
11
|
* spectra --version print the version
|
|
@@ -12,7 +15,7 @@
|
|
|
12
15
|
* root is used for source lookup, unless the matching PERF_* env var overrides it.
|
|
13
16
|
*/
|
|
14
17
|
import { spawn } from 'node:child_process';
|
|
15
|
-
import { readFileSync } from 'node:fs';
|
|
18
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
16
19
|
import path from 'node:path';
|
|
17
20
|
import { fileURLToPath } from 'node:url';
|
|
18
21
|
import { bootDevServer } from '../server/explore/boot.js';
|
|
@@ -33,6 +36,13 @@ Spectra ${pkg.version} — frontend performance profiler (Progen Labs)
|
|
|
33
36
|
|
|
34
37
|
Usage:
|
|
35
38
|
spectra performance [options] Profile a frontend build and report the culprits
|
|
39
|
+
spectra compare <before> <after> [options]
|
|
40
|
+
Compare two profiles; a change counts only when
|
|
41
|
+
it is bigger than the noise between runs
|
|
42
|
+
spectra reports [--json] List saved reports (ids for compare / dashboard)
|
|
43
|
+
spectra mcp [--project <dir>] [--allow-remote]
|
|
44
|
+
Run as an MCP server (stdio) for Claude Code,
|
|
45
|
+
Cursor, VS Code and other AI tools
|
|
36
46
|
spectra dashboard Open the Spectra dashboard UI
|
|
37
47
|
spectra --help, -h Show this help
|
|
38
48
|
spectra --version, -v Print the version
|
|
@@ -52,20 +62,58 @@ performance options:
|
|
|
52
62
|
--headed Show the browser window
|
|
53
63
|
--sign-in <url> Sign-in URL template, e.g. "http://host/login/?redirect={url}"
|
|
54
64
|
|
|
55
|
-
|
|
65
|
+
quick single-page profile (opt in with --quick, --mode or --scenario):
|
|
66
|
+
--quick One page-load profile (about 15 s): the fast loop
|
|
67
|
+
--runs <n> Record n times and report the median (with --quick/--mode)
|
|
56
68
|
--mode live|load Live session or page-load recording
|
|
57
69
|
--duration <seconds> Live recording length (default 8)
|
|
58
70
|
--throttle <n> CPU throttle multiplier (default 1)
|
|
59
71
|
--scenario <file.mjs> Script a live session: export default async page => {…}
|
|
60
72
|
--save-trace Keep the raw Chrome trace alongside the report
|
|
61
73
|
|
|
74
|
+
output and checks (any performance run):
|
|
75
|
+
--json Print a machine-readable result on stdout (progress and
|
|
76
|
+
tables go to stderr)
|
|
77
|
+
--out <file> Also write that result to a file (input for compare)
|
|
78
|
+
--config <file> Budgets file (default: spectra.config.json in the project)
|
|
79
|
+
|
|
80
|
+
compare options:
|
|
81
|
+
<before> <after> Each: a report id, a report file, a --out result file,
|
|
82
|
+
or several ids joined by commas
|
|
83
|
+
--json, --out <file> As above
|
|
84
|
+
--fail-on-regression Exit with code 2 when a key metric got worse
|
|
85
|
+
|
|
86
|
+
Budgets (spectra.config.json in your project root):
|
|
87
|
+
{ "budgets": { "tbtMs": 200, "lcpMs": 2500, "cls": 0.1, "criticalFindings": 0 } }
|
|
88
|
+
|
|
89
|
+
Exit codes: 0 done · 1 could not run · 2 over budget / regression
|
|
90
|
+
|
|
62
91
|
Examples:
|
|
63
92
|
spectra performance # self-boot + golden-path suite
|
|
64
93
|
spectra performance --paths 8 # cap the suite at 8 journeys
|
|
65
94
|
spectra performance --url http://localhost:3000/ # evaluate a running server
|
|
66
|
-
spectra performance --
|
|
95
|
+
spectra performance --quick --runs 5 --out before.json
|
|
96
|
+
spectra performance --quick --runs 5 --out after.json
|
|
97
|
+
spectra compare before.json after.json # did the change help?
|
|
98
|
+
spectra performance --json | jq .summary # pipe it anywhere
|
|
67
99
|
`;
|
|
68
100
|
|
|
101
|
+
// What a terminate signal must clean up: the running script and a dev server
|
|
102
|
+
// Spectra started. Without this, stopping Spectra from outside (an editor or
|
|
103
|
+
// an AI client cancelling a run) would leave the dev server running.
|
|
104
|
+
const active = { child: null, server: null };
|
|
105
|
+
let stopping = false;
|
|
106
|
+
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
107
|
+
process.on(signal, () => {
|
|
108
|
+
if (stopping) return;
|
|
109
|
+
stopping = true;
|
|
110
|
+
active.child?.kill(signal);
|
|
111
|
+
Promise.resolve(active.server?.stop())
|
|
112
|
+
.catch(() => {})
|
|
113
|
+
.finally(() => process.exit(signal === 'SIGINT' ? 130 : 143));
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
69
117
|
function runNodeAsync(scriptRelPath, args, extraEnv = {}) {
|
|
70
118
|
return new Promise((resolve, reject) => {
|
|
71
119
|
const child = spawn(
|
|
@@ -77,7 +125,11 @@ function runNodeAsync(scriptRelPath, args, extraEnv = {}) {
|
|
|
77
125
|
env: { ...process.env, ...extraEnv },
|
|
78
126
|
},
|
|
79
127
|
);
|
|
80
|
-
child
|
|
128
|
+
active.child = child;
|
|
129
|
+
child.on('exit', code => {
|
|
130
|
+
if (active.child === child) active.child = null;
|
|
131
|
+
resolve(code ?? 0);
|
|
132
|
+
});
|
|
81
133
|
child.on('error', reject);
|
|
82
134
|
});
|
|
83
135
|
}
|
|
@@ -91,6 +143,19 @@ function takeOption(args, name) {
|
|
|
91
143
|
return { value, rest: copy };
|
|
92
144
|
}
|
|
93
145
|
|
|
146
|
+
/**
|
|
147
|
+
* The scripts run with the package as their working directory, so path
|
|
148
|
+
* options the user typed relative to their own directory are made absolute
|
|
149
|
+
* here, before they cross over.
|
|
150
|
+
*/
|
|
151
|
+
function absolutize(args, names) {
|
|
152
|
+
return args.map((a, i) =>
|
|
153
|
+
i > 0 && names.includes(args[i - 1].replace(/^--/, '')) && args[i - 1].startsWith('--') && !a.startsWith('--')
|
|
154
|
+
? path.resolve(process.cwd(), a)
|
|
155
|
+
: a,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
94
159
|
/**
|
|
95
160
|
* Default the profiled project and the reports location to the user's current
|
|
96
161
|
* directory, so an installed Spectra profiles the host project and writes
|
|
@@ -114,27 +179,44 @@ async function runProfile() {
|
|
|
114
179
|
return 0;
|
|
115
180
|
}
|
|
116
181
|
const env = projectEnv();
|
|
182
|
+
let args = absolutize(rest, ['scenario', 'out', 'config']);
|
|
183
|
+
|
|
184
|
+
// --quick is shorthand for a single page-load profile.
|
|
185
|
+
if (args.includes('--quick')) {
|
|
186
|
+
const mode = takeOption(args, 'mode').value;
|
|
187
|
+
if ((mode && mode !== 'load') || args.includes('--scenario')) {
|
|
188
|
+
console.error('spectra: --quick is a page-load profile; it cannot be combined with --mode live or --scenario.');
|
|
189
|
+
return 1;
|
|
190
|
+
}
|
|
191
|
+
args = args.filter(a => a !== '--quick');
|
|
192
|
+
if (!mode) args.push('--mode', 'load');
|
|
193
|
+
}
|
|
117
194
|
|
|
118
195
|
// Golden-path evaluation is the default. --mode/--scenario opt into the
|
|
119
196
|
// classic single-page profile instead.
|
|
120
|
-
const classic =
|
|
197
|
+
const classic = args.includes('--mode') || args.includes('--scenario');
|
|
121
198
|
const script = classic ? 'server/cli.js' : 'server/explore/evaluate-cli.js';
|
|
199
|
+
if (!classic && args.includes('--runs')) {
|
|
200
|
+
console.error(
|
|
201
|
+
'spectra: --runs works with single-page profiles (--quick, --mode or --scenario); the golden-path suite records each journey once.',
|
|
202
|
+
);
|
|
203
|
+
return 1;
|
|
204
|
+
}
|
|
122
205
|
|
|
123
206
|
// An explicit --url means the server is already running: run against it.
|
|
124
|
-
if (
|
|
125
|
-
return runNodeAsync(script,
|
|
207
|
+
if (args.includes('--url')) {
|
|
208
|
+
return runNodeAsync(script, args, env);
|
|
126
209
|
}
|
|
127
210
|
|
|
128
211
|
// No --url: self-boot the target project's own dev server, run, stop.
|
|
129
212
|
const cwd = process.cwd();
|
|
130
|
-
const { value: command, rest: afterCmd } = takeOption(
|
|
213
|
+
const { value: command, rest: afterCmd } = takeOption(args, 'command');
|
|
131
214
|
const { value: bootTimeout, rest: profileArgs } = takeOption(afterCmd, 'boot-timeout');
|
|
132
215
|
const fw = detectFramework(cwd);
|
|
133
|
-
const label =
|
|
134
|
-
fw.framework !== 'unknown' ? `${fw.framework}/${fw.bundler}` : fw.bundler;
|
|
216
|
+
const label = [fw.framework, fw.bundler].filter(x => x && x !== 'unknown').join('/');
|
|
135
217
|
// Status goes to stderr so it never mixes with the report on stdout.
|
|
136
218
|
console.error(
|
|
137
|
-
`spectra: no --url given — starting the ${label} dev server in ${cwd} …`,
|
|
219
|
+
`spectra: no --url given — starting the ${label ? `${label} ` : ''}dev server in ${cwd} …`,
|
|
138
220
|
);
|
|
139
221
|
|
|
140
222
|
let server;
|
|
@@ -148,6 +230,7 @@ async function runProfile() {
|
|
|
148
230
|
console.error(`spectra: ${err.message}`);
|
|
149
231
|
return 1;
|
|
150
232
|
}
|
|
233
|
+
active.server = server;
|
|
151
234
|
console.error(`spectra: dev server ready at ${server.url} — profiling …\n`);
|
|
152
235
|
|
|
153
236
|
let code = 1;
|
|
@@ -158,12 +241,23 @@ async function runProfile() {
|
|
|
158
241
|
env,
|
|
159
242
|
);
|
|
160
243
|
} finally {
|
|
244
|
+
active.server = null;
|
|
161
245
|
await server.stop();
|
|
162
246
|
console.error('\nspectra: stopped the dev server.');
|
|
163
247
|
}
|
|
164
248
|
return code;
|
|
165
249
|
}
|
|
166
250
|
|
|
251
|
+
function runCompare() {
|
|
252
|
+
// Sides may be files relative to the user's directory (ids pass through).
|
|
253
|
+
const fixToken = t =>
|
|
254
|
+
t.endsWith('.json') || existsSync(path.resolve(process.cwd(), t)) ? path.resolve(process.cwd(), t) : t;
|
|
255
|
+
const args = absolutize(rest, ['out']).map((a, i, all) =>
|
|
256
|
+
a.startsWith('--') || all[i - 1] === '--out' ? a : a.split(',').map(fixToken).join(','),
|
|
257
|
+
);
|
|
258
|
+
return runNodeAsync('server/compare-cli.js', args, projectEnv());
|
|
259
|
+
}
|
|
260
|
+
|
|
167
261
|
function runDashboard() {
|
|
168
262
|
// Standalone server: serves the prebuilt dashboard and the backend API, so an
|
|
169
263
|
// installed Spectra needs no dev toolchain. projectEnv() points it at this
|
|
@@ -177,6 +271,7 @@ function runDashboard() {
|
|
|
177
271
|
env: { ...process.env, ...projectEnv() },
|
|
178
272
|
},
|
|
179
273
|
);
|
|
274
|
+
active.child = child;
|
|
180
275
|
child.on('exit', code => process.exit(code ?? 0));
|
|
181
276
|
child.on('error', err => {
|
|
182
277
|
console.error(`spectra: failed to start the dashboard — ${err.message}`);
|
|
@@ -194,6 +289,26 @@ switch (command) {
|
|
|
194
289
|
process.exit(1);
|
|
195
290
|
});
|
|
196
291
|
break;
|
|
292
|
+
case 'compare':
|
|
293
|
+
case 'reports': {
|
|
294
|
+
const run = command === 'compare' ? runCompare() : runNodeAsync('server/reports-cli.js', rest, projectEnv());
|
|
295
|
+
run
|
|
296
|
+
.then(code => process.exit(code ?? 0))
|
|
297
|
+
.catch(err => {
|
|
298
|
+
console.error(`spectra: ${err.message}`);
|
|
299
|
+
process.exit(1);
|
|
300
|
+
});
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
case 'mcp':
|
|
304
|
+
// In-process: stdin/stdout are the protocol channel, and the working
|
|
305
|
+
// directory stays the user's (the project, unless --project or the
|
|
306
|
+
// client's roots say otherwise).
|
|
307
|
+
import('../server/mcp.js').catch(err => {
|
|
308
|
+
console.error(`spectra: ${err.message}`);
|
|
309
|
+
process.exit(1);
|
|
310
|
+
});
|
|
311
|
+
break;
|
|
197
312
|
case 'dashboard':
|
|
198
313
|
case 'ui':
|
|
199
314
|
runDashboard();
|