loadscope 0.0.0-stage ā 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 +21 -0
- package/README.md +292 -3
- package/dist/chunk-7L4SENK7.js +269 -0
- package/dist/chunk-7L4SENK7.js.map +1 -0
- package/dist/chunk-OFND2NUO.js +3007 -0
- package/dist/chunk-OFND2NUO.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +361 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +720 -0
- package/dist/index.js +87 -0
- package/dist/index.js.map +1 -0
- package/dist/worker.d.ts +2 -0
- package/dist/worker.js +199 -0
- package/dist/worker.js.map +1 -0
- package/package.json +67 -6
package/dist/cli.js
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
buildAiDiagnosticPrompt,
|
|
4
|
+
compareRuns,
|
|
5
|
+
fetchAiDiagnostic,
|
|
6
|
+
formatBreakpointCsvReport,
|
|
7
|
+
formatBreakpointJsonReport,
|
|
8
|
+
formatComparisonCsvReport,
|
|
9
|
+
formatComparisonJsonReport,
|
|
10
|
+
formatCsvReport,
|
|
11
|
+
formatJsonReport,
|
|
12
|
+
formatStepRow,
|
|
13
|
+
generateHtmlReport,
|
|
14
|
+
generatePdfReport,
|
|
15
|
+
loadBaseline,
|
|
16
|
+
parseHeaders,
|
|
17
|
+
parseScenario,
|
|
18
|
+
printBreakpointTerminalReport,
|
|
19
|
+
printComparisonTerminalReport,
|
|
20
|
+
printScenarioTerminalReport,
|
|
21
|
+
printTerminalReport,
|
|
22
|
+
run,
|
|
23
|
+
runBreakingPoint,
|
|
24
|
+
runScenario,
|
|
25
|
+
saveBaseline,
|
|
26
|
+
writeGithubStepSummary
|
|
27
|
+
} from "./chunk-OFND2NUO.js";
|
|
28
|
+
import "./chunk-7L4SENK7.js";
|
|
29
|
+
|
|
30
|
+
// src/cli.ts
|
|
31
|
+
import { Command } from "commander";
|
|
32
|
+
import picocolors from "picocolors";
|
|
33
|
+
import fs from "fs/promises";
|
|
34
|
+
var program = new Command();
|
|
35
|
+
program.name("loadscope").description("A high-performance load-testing CLI with breaking-point finder and CI regression gate").version("0.1.0");
|
|
36
|
+
program.command("break <url>").alias("breakpoint").description("Automatically find the breaking-point and maximum safe capacity").option("--start <number>", "Initial load (concurrency or rate)", "10").option("--max <number>", "Maximum ceiling load to test", "100").option("--step <number>", "Step increment", "10").option("--step-duration <duration>", "Evaluation duration for each step", "5s").option("--max-p95 <ms>", "Max p95 latency before breach (ms)", "300").option("--max-p99 <ms>", "Max p99 latency before breach (ms)").option("--max-errors <pct>", "Max error rate percentage before breach (e.g. 1.0)", "1.0").option("-m, --mode <mode>", "Load type: 'concurrency' or 'rate'", "concurrency").option("--no-refine", "Disable binary search refinement").option("--tolerance <number>", "Binary search precision tolerance", "2").option("-W, --workers <number|auto>", "Worker threads count").option("-H, --header <headers...>", "Custom HTTP header(s)").option("-b, --body <body>", "HTTP request body").option("--method <method>", "HTTP method (GET, POST, etc.)", "GET").option("--timeout <duration>", "Request timeout", "10s").option("--honest", "Enable Gil Tene Coordinated Omission correction", true).option("--distribution <distribution>", "Arrival process ('uniform' or 'poisson')", "uniform").option("--json [path]", "Export machine-readable JSON summary report (to path or stdout)").option("--csv [path]", "Export machine-readable CSV report (to path or stdout)").action(async (url, options) => {
|
|
37
|
+
try {
|
|
38
|
+
const isSilentStdout = options.json === true || options.csv === true;
|
|
39
|
+
if (!isSilentStdout) {
|
|
40
|
+
console.log(
|
|
41
|
+
picocolors.cyan(
|
|
42
|
+
`
|
|
43
|
+
\u{1F50D} Initializing LoadScope Breaking-Point Finder against ${picocolors.bold(url)}...`
|
|
44
|
+
)
|
|
45
|
+
);
|
|
46
|
+
console.log(
|
|
47
|
+
picocolors.dim(
|
|
48
|
+
` Ramp: ${options.start} \u2794 ${options.max} (step: ${options.step}) | Step Duration: ${options.stepDuration} | Max p95: ${options.maxP95}ms | Max Errors: ${options.maxErrors}%`
|
|
49
|
+
)
|
|
50
|
+
);
|
|
51
|
+
console.log("");
|
|
52
|
+
}
|
|
53
|
+
const headers = options.header ? parseHeaders(options.header) : void 0;
|
|
54
|
+
const maxErrorRate = parseFloat(options.maxErrors) / 100;
|
|
55
|
+
const summary = await runBreakingPoint({
|
|
56
|
+
url,
|
|
57
|
+
mode: options.mode,
|
|
58
|
+
start: parseInt(options.start, 10),
|
|
59
|
+
max: parseInt(options.max, 10),
|
|
60
|
+
step: parseInt(options.step, 10),
|
|
61
|
+
stepDuration: options.stepDuration,
|
|
62
|
+
maxP95: options.maxP95 ? parseFloat(options.maxP95) : void 0,
|
|
63
|
+
maxP99: options.maxP99 ? parseFloat(options.maxP99) : void 0,
|
|
64
|
+
maxErrorRate,
|
|
65
|
+
refine: options.refine !== false,
|
|
66
|
+
tolerance: parseInt(options.tolerance, 10),
|
|
67
|
+
workers: options.workers === "auto" ? "auto" : options.workers ? parseInt(options.workers, 10) : 1,
|
|
68
|
+
method: options.method.toUpperCase(),
|
|
69
|
+
headers,
|
|
70
|
+
body: options.body,
|
|
71
|
+
timeout: options.timeout,
|
|
72
|
+
honest: options.honest,
|
|
73
|
+
distribution: options.distribution,
|
|
74
|
+
onStepStart: (stepNum, load, isRefinement) => {
|
|
75
|
+
if (!isSilentStdout) {
|
|
76
|
+
const label = isRefinement ? "Refining" : "Testing";
|
|
77
|
+
process.stdout.write(picocolors.dim(` [${stepNum}] ${label} load = ${load}... `));
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
onStepComplete: (result) => {
|
|
81
|
+
if (!isSilentStdout) {
|
|
82
|
+
process.stdout.write("\r");
|
|
83
|
+
console.log(formatStepRow(result, options.mode));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
if (typeof options.json === "string") {
|
|
88
|
+
await fs.writeFile(options.json, formatBreakpointJsonReport(summary), "utf-8");
|
|
89
|
+
console.log(picocolors.green(`
|
|
90
|
+
\u{1F4C4} Saved JSON report to: ${options.json}`));
|
|
91
|
+
} else if (options.json === true) {
|
|
92
|
+
console.log(formatBreakpointJsonReport(summary));
|
|
93
|
+
}
|
|
94
|
+
if (typeof options.csv === "string") {
|
|
95
|
+
await fs.writeFile(options.csv, formatBreakpointCsvReport(summary), "utf-8");
|
|
96
|
+
console.log(picocolors.green(`
|
|
97
|
+
\u{1F4CA} Saved CSV report to: ${options.csv}`));
|
|
98
|
+
} else if (options.csv === true) {
|
|
99
|
+
console.log(formatBreakpointCsvReport(summary));
|
|
100
|
+
}
|
|
101
|
+
if (!isSilentStdout) {
|
|
102
|
+
printBreakpointTerminalReport(summary);
|
|
103
|
+
}
|
|
104
|
+
} catch (err) {
|
|
105
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
106
|
+
console.error("\n" + picocolors.red(`\u274C LoadScope breakpoint execution failed: ${errorMsg}
|
|
107
|
+
`));
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
program.command("scenario <file>").description("Execute a multi-step user journey scenario from a YAML or JSON definition").option("-c, --concurrency <number>", "Override scenario concurrency").option("-d, --duration <duration>", "Override scenario duration (e.g. 10s, 1m)").option("--timeout <duration>", "Override request timeout").option("--json [path]", "Export machine-readable JSON scenario summary").option("--html [path]", "Generate interactive HTML report for scenario").option("--pdf <path>", "Generate vector PDF report for scenario").action(async (file, options) => {
|
|
112
|
+
try {
|
|
113
|
+
const isSilentStdout = options.json === true;
|
|
114
|
+
if (!isSilentStdout) {
|
|
115
|
+
console.log(picocolors.cyan(`
|
|
116
|
+
\u{1F3AD} Initializing LoadScope Scenario Runner: ${picocolors.bold(file)}...`));
|
|
117
|
+
}
|
|
118
|
+
const definition = parseScenario(file);
|
|
119
|
+
const summary = await runScenario(definition, {
|
|
120
|
+
concurrency: options.concurrency ? parseInt(options.concurrency, 10) : void 0,
|
|
121
|
+
duration: options.duration,
|
|
122
|
+
timeout: options.timeout
|
|
123
|
+
});
|
|
124
|
+
if (typeof options.json === "string") {
|
|
125
|
+
await fs.writeFile(options.json, JSON.stringify(summary, null, 2), "utf-8");
|
|
126
|
+
console.log(picocolors.green(`
|
|
127
|
+
\u{1F4C4} Saved JSON report to: ${options.json}`));
|
|
128
|
+
} else if (options.json === true) {
|
|
129
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
130
|
+
}
|
|
131
|
+
if (options.html) {
|
|
132
|
+
const htmlPath = typeof options.html === "string" ? options.html : "loadscope-scenario-report.html";
|
|
133
|
+
const resolved = await generateHtmlReport(summary.overallSummary, htmlPath);
|
|
134
|
+
if (!isSilentStdout) {
|
|
135
|
+
console.log(picocolors.green(`
|
|
136
|
+
\u{1F310} Generated interactive HTML report: ${resolved}`));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (options.pdf) {
|
|
140
|
+
const resolvedPdf = await generatePdfReport(summary.overallSummary, options.pdf);
|
|
141
|
+
if (!isSilentStdout) {
|
|
142
|
+
console.log(picocolors.green(`
|
|
143
|
+
\u{1F4C4} Generated vector PDF report: ${resolvedPdf}`));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (!isSilentStdout) {
|
|
147
|
+
printScenarioTerminalReport(summary);
|
|
148
|
+
}
|
|
149
|
+
} catch (err) {
|
|
150
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
151
|
+
console.error("\n" + picocolors.red(`\u274C LoadScope scenario execution failed: ${errorMsg}
|
|
152
|
+
`));
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
program.command("compare <current> <baseline>").description("Compare two performance JSON reports against regression tolerances").option("--max-p95-increase <pct>", "Allowable percentage increase in p95 latency", "10").option("--max-rps-drop <pct>", "Allowable percentage drop in throughput", "5").option("--max-p99-increase <pct>", "Allowable percentage increase in p99 latency").option("--max-errors <pct>", "Max error rate percentage before regression (e.g. 1.0)", "1.0").option("--json [path]", "Export machine-readable comparison JSON (to path or stdout)").option("--csv [path]", "Export machine-readable comparison CSV (to path or stdout)").action(async (currentPath, baselinePath, options) => {
|
|
157
|
+
try {
|
|
158
|
+
const isSilentStdout = options.json === true || options.csv === true;
|
|
159
|
+
const currentRaw = JSON.parse(await fs.readFile(currentPath, "utf-8"));
|
|
160
|
+
const baselineRaw = JSON.parse(await fs.readFile(baselinePath, "utf-8"));
|
|
161
|
+
const comparison = compareRuns(currentRaw, baselineRaw, {
|
|
162
|
+
maxP95IncreasePct: parseFloat(options.maxP95Increase),
|
|
163
|
+
maxRpsDropPct: parseFloat(options.maxRpsDrop),
|
|
164
|
+
maxP99IncreasePct: options.maxP99Increase ? parseFloat(options.maxP99Increase) : void 0,
|
|
165
|
+
maxErrorRate: options.maxErrors ? parseFloat(options.maxErrors) / 100 : 0.01
|
|
166
|
+
});
|
|
167
|
+
if (typeof options.json === "string") {
|
|
168
|
+
await fs.writeFile(options.json, formatComparisonJsonReport(comparison), "utf-8");
|
|
169
|
+
console.log(picocolors.green(`
|
|
170
|
+
\u{1F4C4} Saved comparison JSON to: ${options.json}`));
|
|
171
|
+
} else if (options.json === true) {
|
|
172
|
+
console.log(formatComparisonJsonReport(comparison));
|
|
173
|
+
}
|
|
174
|
+
if (typeof options.csv === "string") {
|
|
175
|
+
await fs.writeFile(options.csv, formatComparisonCsvReport(comparison), "utf-8");
|
|
176
|
+
console.log(picocolors.green(`
|
|
177
|
+
\u{1F4CA} Saved comparison CSV to: ${options.csv}`));
|
|
178
|
+
} else if (options.csv === true) {
|
|
179
|
+
console.log(formatComparisonCsvReport(comparison));
|
|
180
|
+
}
|
|
181
|
+
if (!isSilentStdout) {
|
|
182
|
+
printComparisonTerminalReport(comparison);
|
|
183
|
+
}
|
|
184
|
+
await writeGithubStepSummary(comparison);
|
|
185
|
+
if (!comparison.passed) {
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
} catch (err) {
|
|
189
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
190
|
+
console.error("\n" + picocolors.red(`\u274C LoadScope compare failed: ${errorMsg}
|
|
191
|
+
`));
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
program.option("-u, --url <url>", "Target URL to test (e.g. http://localhost:3000)").option("-c, --concurrency <number>", "Number of concurrent connections", "10").option("-d, --duration <duration>", "Test duration (e.g. 10s, 1m, 500ms)", "10s").option("-m, --method <method>", "HTTP method (GET, POST, PUT, DELETE)", "GET").option("-H, --header <headers...>", "Custom HTTP header(s) (e.g. -H 'Authorization: Bearer token')").option("-b, --body <body>", "HTTP request body").option("-r, --rate <number>", "Requests per second (open arrival model)").option("--distribution <distribution>", "Arrival process distribution ('uniform' or 'poisson')", "uniform").option("--honest", "Enable Gil Tene Coordinated Omission correction").option("--ttfb", "Track Time-To-First-Byte (TTFB) latency breakdown").option("-w, --warmup <duration>", "Warmup duration before recording metrics (e.g. 2s)").option("-W, --workers <number|auto>", "Number of worker threads for multi-core scaling (default: 1)").option("--timeout <duration>", "Per-request timeout (e.g. 5s, 500ms)", "10s").option("--ramp-up <duration>", "Ramp-up duration to stagger worker starts (e.g. 3s)").option("--mode <mode>", "Execution model ('closed' or 'open')").option("--dashboard", "Enable real-time interactive terminal dashboard", true).option("--no-dashboard", "Disable real-time interactive terminal dashboard").option("--break", "Run in breaking-point finder mode").option("--start <number>", "Initial load for breaking-point mode", "10").option("--max <number>", "Maximum ceiling load for breaking-point mode", "100").option("--step <number>", "Step increment for breaking-point mode", "10").option("--step-duration <duration>", "Duration per step for breaking-point mode", "5s").option("--max-p95 <ms>", "Max p95 latency before breach (ms)", "300").option("--max-p99 <ms>", "Max p99 latency before breach (ms)").option("--max-errors <pct>", "Max error rate percentage before breach (e.g. 1.0)", "1.0").option("--baseline <path>", "Compare test results against a baseline JSON file and fail on regression").option("--save-baseline [path]", "Save the current test run as a baseline file (default: loadscope-baseline.json)").option("--max-p95-increase <pct>", "Allowable percentage increase in p95 latency for baseline comparison", "10").option("--max-rps-drop <pct>", "Allowable percentage drop in throughput for baseline comparison", "5").option("--max-p99-increase <pct>", "Allowable percentage increase in p99 latency for baseline comparison").option("--json [path]", "Export machine-readable JSON summary report (to path or stdout)").option("--csv [path]", "Export machine-readable CSV summary and time-series report (to path or stdout)").option("--html [path]", "Generate a self-contained interactive HTML report (default: loadscope-report.html)").option("--pdf <path>", "Generate a PDF report at the specified path").option("--ai-report <path>", "Generate a PDF report including AI analysis (requires LLM_API_KEY)").option("--ai-dry-run", "Preview prompt that would be sent to LLM without calling API").action(async (options) => {
|
|
196
|
+
if (!options.url) {
|
|
197
|
+
program.help();
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
const isSilentStdout = options.json === true || options.csv === true;
|
|
202
|
+
if (options.break) {
|
|
203
|
+
const headers2 = options.header ? parseHeaders(options.header) : void 0;
|
|
204
|
+
const maxErrorRate = parseFloat(options.maxErrors || "1.0") / 100;
|
|
205
|
+
const summary2 = await runBreakingPoint({
|
|
206
|
+
url: options.url,
|
|
207
|
+
mode: options.mode === "open" || options.rate ? "rate" : "concurrency",
|
|
208
|
+
start: parseInt(options.start, 10),
|
|
209
|
+
max: parseInt(options.max, 10),
|
|
210
|
+
step: parseInt(options.step, 10),
|
|
211
|
+
stepDuration: options.stepDuration,
|
|
212
|
+
maxP95: options.maxP95 ? parseFloat(options.maxP95) : void 0,
|
|
213
|
+
maxP99: options.maxP99 ? parseFloat(options.maxP99) : void 0,
|
|
214
|
+
maxErrorRate,
|
|
215
|
+
refine: true,
|
|
216
|
+
workers: options.workers === "auto" ? "auto" : options.workers ? parseInt(options.workers, 10) : 1,
|
|
217
|
+
method: options.method.toUpperCase(),
|
|
218
|
+
headers: headers2,
|
|
219
|
+
body: options.body,
|
|
220
|
+
timeout: options.timeout,
|
|
221
|
+
honest: options.honest,
|
|
222
|
+
distribution: options.distribution
|
|
223
|
+
});
|
|
224
|
+
if (typeof options.json === "string") {
|
|
225
|
+
await fs.writeFile(options.json, formatBreakpointJsonReport(summary2), "utf-8");
|
|
226
|
+
console.log(picocolors.green(`
|
|
227
|
+
\u{1F4C4} Saved JSON report to: ${options.json}`));
|
|
228
|
+
} else if (options.json === true) {
|
|
229
|
+
console.log(formatBreakpointJsonReport(summary2));
|
|
230
|
+
}
|
|
231
|
+
if (typeof options.csv === "string") {
|
|
232
|
+
await fs.writeFile(options.csv, formatBreakpointCsvReport(summary2), "utf-8");
|
|
233
|
+
console.log(picocolors.green(`
|
|
234
|
+
\u{1F4CA} Saved CSV report to: ${options.csv}`));
|
|
235
|
+
} else if (options.csv === true) {
|
|
236
|
+
console.log(formatBreakpointCsvReport(summary2));
|
|
237
|
+
}
|
|
238
|
+
if (!isSilentStdout) {
|
|
239
|
+
printBreakpointTerminalReport(summary2);
|
|
240
|
+
}
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (!isSilentStdout) {
|
|
244
|
+
console.log(picocolors.cyan(`
|
|
245
|
+
\u{1F680} Initializing LoadScope runner against ${picocolors.bold(options.url)}...`));
|
|
246
|
+
const modeDesc = options.rate || options.mode === "open" ? `Open (${options.distribution.toUpperCase()} ${options.rate ?? 50} req/s)` : `Closed (${options.concurrency} conn)`;
|
|
247
|
+
const workersDesc = options.workers ? ` | Workers: ${options.workers}` : "";
|
|
248
|
+
const honestDesc = options.honest ? " | Honest CO: ON" : "";
|
|
249
|
+
console.log(
|
|
250
|
+
picocolors.dim(
|
|
251
|
+
` Mode: ${modeDesc}${workersDesc}${honestDesc} | Duration: ${options.duration} | Method: ${options.method.toUpperCase()}`
|
|
252
|
+
)
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
const headers = options.header ? parseHeaders(options.header) : void 0;
|
|
256
|
+
const summary = await run({
|
|
257
|
+
url: options.url,
|
|
258
|
+
concurrency: parseInt(options.concurrency, 10),
|
|
259
|
+
duration: options.duration,
|
|
260
|
+
method: options.method.toUpperCase(),
|
|
261
|
+
headers,
|
|
262
|
+
body: options.body,
|
|
263
|
+
rate: options.rate ? parseFloat(options.rate) : void 0,
|
|
264
|
+
distribution: options.distribution,
|
|
265
|
+
honest: options.honest,
|
|
266
|
+
ttfb: options.ttfb,
|
|
267
|
+
warmup: options.warmup,
|
|
268
|
+
timeout: options.timeout,
|
|
269
|
+
rampUp: options.rampUp,
|
|
270
|
+
mode: options.mode,
|
|
271
|
+
workers: options.workers === "auto" ? "auto" : options.workers ? parseInt(options.workers, 10) : 1,
|
|
272
|
+
dashboard: isSilentStdout ? false : options.dashboard,
|
|
273
|
+
json: options.json,
|
|
274
|
+
csv: options.csv,
|
|
275
|
+
pdf: options.pdf,
|
|
276
|
+
aiReport: options.aiReport,
|
|
277
|
+
aiDryRun: options.aiDryRun
|
|
278
|
+
});
|
|
279
|
+
if (typeof options.json === "string") {
|
|
280
|
+
await fs.writeFile(options.json, formatJsonReport(summary), "utf-8");
|
|
281
|
+
console.log(picocolors.green(`
|
|
282
|
+
\u{1F4C4} Saved JSON report to: ${options.json}`));
|
|
283
|
+
} else if (options.json === true) {
|
|
284
|
+
console.log(formatJsonReport(summary));
|
|
285
|
+
}
|
|
286
|
+
if (typeof options.csv === "string") {
|
|
287
|
+
await fs.writeFile(options.csv, formatCsvReport(summary), "utf-8");
|
|
288
|
+
console.log(picocolors.green(`
|
|
289
|
+
\u{1F4CA} Saved CSV report to: ${options.csv}`));
|
|
290
|
+
} else if (options.csv === true) {
|
|
291
|
+
console.log(formatCsvReport(summary));
|
|
292
|
+
}
|
|
293
|
+
if (!isSilentStdout) {
|
|
294
|
+
printTerminalReport(summary);
|
|
295
|
+
}
|
|
296
|
+
if (options.aiDryRun) {
|
|
297
|
+
const prompt = buildAiDiagnosticPrompt(summary);
|
|
298
|
+
console.log(picocolors.cyan("\n\u{1F916} LoadScope AI Diagnostic Prompt Preview (--ai-dry-run):"));
|
|
299
|
+
console.log(picocolors.dim("\u2500".repeat(70)));
|
|
300
|
+
console.log(prompt);
|
|
301
|
+
console.log(picocolors.dim("\u2500".repeat(70)));
|
|
302
|
+
}
|
|
303
|
+
if (options.pdf || options.aiReport) {
|
|
304
|
+
const pdfPath = typeof options.aiReport === "string" ? options.aiReport : options.pdf;
|
|
305
|
+
let aiAnalysis;
|
|
306
|
+
if (options.aiReport) {
|
|
307
|
+
if (!isSilentStdout) {
|
|
308
|
+
console.log(picocolors.cyan("\n\u{1F916} Generating AI Performance Diagnostic..."));
|
|
309
|
+
}
|
|
310
|
+
aiAnalysis = await fetchAiDiagnostic(summary);
|
|
311
|
+
}
|
|
312
|
+
const resolvedPdf = await generatePdfReport(summary, pdfPath, aiAnalysis);
|
|
313
|
+
if (!isSilentStdout) {
|
|
314
|
+
console.log(picocolors.green(`
|
|
315
|
+
\u{1F4C4} Generated vector PDF report: ${resolvedPdf}`));
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (options.html) {
|
|
319
|
+
const htmlPath = typeof options.html === "string" ? options.html : "loadscope-report.html";
|
|
320
|
+
const resolvedHtml = await generateHtmlReport(summary, htmlPath);
|
|
321
|
+
if (!isSilentStdout) {
|
|
322
|
+
console.log(picocolors.green(`
|
|
323
|
+
\u{1F310} Generated interactive HTML report: ${resolvedHtml}`));
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (options.saveBaseline) {
|
|
327
|
+
const baselinePath = typeof options.saveBaseline === "string" ? options.saveBaseline : "loadscope-baseline.json";
|
|
328
|
+
const savedTo = await saveBaseline(summary, baselinePath, {
|
|
329
|
+
concurrency: parseInt(options.concurrency, 10),
|
|
330
|
+
rate: options.rate ? parseFloat(options.rate) : void 0
|
|
331
|
+
});
|
|
332
|
+
if (!isSilentStdout) {
|
|
333
|
+
console.log(picocolors.green(`
|
|
334
|
+
\u{1F4BE} Saved performance baseline to: ${savedTo}`));
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (options.baseline) {
|
|
338
|
+
const baseline = await loadBaseline(options.baseline);
|
|
339
|
+
const comparison = compareRuns(summary, baseline, {
|
|
340
|
+
maxP95IncreasePct: parseFloat(options.maxP95Increase || "10"),
|
|
341
|
+
maxRpsDropPct: parseFloat(options.maxRpsDrop || "5"),
|
|
342
|
+
maxP99IncreasePct: options.maxP99Increase ? parseFloat(options.maxP99Increase) : void 0,
|
|
343
|
+
maxErrorRate: options.maxErrors ? parseFloat(options.maxErrors) / 100 : 0.01
|
|
344
|
+
});
|
|
345
|
+
if (!isSilentStdout) {
|
|
346
|
+
printComparisonTerminalReport(comparison);
|
|
347
|
+
}
|
|
348
|
+
await writeGithubStepSummary(comparison);
|
|
349
|
+
if (!comparison.passed) {
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
} catch (err) {
|
|
354
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
355
|
+
console.error("\n" + picocolors.red(`\u274C LoadScope execution failed: ${errorMsg}
|
|
356
|
+
`));
|
|
357
|
+
process.exit(1);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
program.parse(process.argv);
|
|
361
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\r\nimport { Command } from \"commander\";\r\nimport picocolors from \"picocolors\";\r\nimport fs from \"node:fs/promises\";\r\nimport {\r\n run,\r\n runBreakingPoint,\r\n generatePdfReport,\r\n buildAiDiagnosticPrompt,\r\n fetchAiDiagnostic,\r\n generateHtmlReport,\r\n parseScenario,\r\n runScenario,\r\n printScenarioTerminalReport,\r\n} from \"./index.js\";\r\nimport { printTerminalReport } from \"./report/terminal.js\";\r\nimport { printBreakpointTerminalReport, formatStepRow } from \"./report/breakpoint-terminal.js\";\r\nimport {\r\n formatJsonReport,\r\n formatCsvReport,\r\n formatBreakpointJsonReport,\r\n formatBreakpointCsvReport,\r\n formatComparisonJsonReport,\r\n formatComparisonCsvReport,\r\n} from \"./report/export.js\";\r\nimport { parseHeaders } from \"./config/schema.js\";\r\nimport { saveBaseline, loadBaseline, compareRuns } from \"./modes/regression-gate.js\";\r\nimport {\r\n printComparisonTerminalReport,\r\n writeGithubStepSummary,\r\n} from \"./report/compare-terminal.js\";\r\n\r\nconst program = new Command();\r\n\r\nprogram\r\n .name(\"loadscope\")\r\n .description(\"A high-performance load-testing CLI with breaking-point finder and CI regression gate\")\r\n .version(\"0.1.0\");\r\n\r\n// Subcommand: break / breakpoint\r\nprogram\r\n .command(\"break <url>\")\r\n .alias(\"breakpoint\")\r\n .description(\"Automatically find the breaking-point and maximum safe capacity\")\r\n .option(\"--start <number>\", \"Initial load (concurrency or rate)\", \"10\")\r\n .option(\"--max <number>\", \"Maximum ceiling load to test\", \"100\")\r\n .option(\"--step <number>\", \"Step increment\", \"10\")\r\n .option(\"--step-duration <duration>\", \"Evaluation duration for each step\", \"5s\")\r\n .option(\"--max-p95 <ms>\", \"Max p95 latency before breach (ms)\", \"300\")\r\n .option(\"--max-p99 <ms>\", \"Max p99 latency before breach (ms)\")\r\n .option(\"--max-errors <pct>\", \"Max error rate percentage before breach (e.g. 1.0)\", \"1.0\")\r\n .option(\"-m, --mode <mode>\", \"Load type: 'concurrency' or 'rate'\", \"concurrency\")\r\n .option(\"--no-refine\", \"Disable binary search refinement\")\r\n .option(\"--tolerance <number>\", \"Binary search precision tolerance\", \"2\")\r\n .option(\"-W, --workers <number|auto>\", \"Worker threads count\")\r\n .option(\"-H, --header <headers...>\", \"Custom HTTP header(s)\")\r\n .option(\"-b, --body <body>\", \"HTTP request body\")\r\n .option(\"--method <method>\", \"HTTP method (GET, POST, etc.)\", \"GET\")\r\n .option(\"--timeout <duration>\", \"Request timeout\", \"10s\")\r\n .option(\"--honest\", \"Enable Gil Tene Coordinated Omission correction\", true)\r\n .option(\"--distribution <distribution>\", \"Arrival process ('uniform' or 'poisson')\", \"uniform\")\r\n .option(\"--json [path]\", \"Export machine-readable JSON summary report (to path or stdout)\")\r\n .option(\"--csv [path]\", \"Export machine-readable CSV report (to path or stdout)\")\r\n .action(async (url, options) => {\r\n try {\r\n const isSilentStdout = options.json === true || options.csv === true;\r\n\r\n if (!isSilentStdout) {\r\n console.log(\r\n picocolors.cyan(\r\n `\\nš Initializing LoadScope Breaking-Point Finder against ${picocolors.bold(url)}...`\r\n )\r\n );\r\n console.log(\r\n picocolors.dim(\r\n ` Ramp: ${options.start} ā ${options.max} (step: ${options.step}) | Step Duration: ${options.stepDuration} | Max p95: ${options.maxP95}ms | Max Errors: ${options.maxErrors}%`\r\n )\r\n );\r\n console.log(\"\");\r\n }\r\n\r\n const headers = options.header ? parseHeaders(options.header) : undefined;\r\n const maxErrorRate = parseFloat(options.maxErrors) / 100;\r\n\r\n const summary = await runBreakingPoint({\r\n url,\r\n mode: options.mode as \"concurrency\" | \"rate\",\r\n start: parseInt(options.start, 10),\r\n max: parseInt(options.max, 10),\r\n step: parseInt(options.step, 10),\r\n stepDuration: options.stepDuration,\r\n maxP95: options.maxP95 ? parseFloat(options.maxP95) : undefined,\r\n maxP99: options.maxP99 ? parseFloat(options.maxP99) : undefined,\r\n maxErrorRate,\r\n refine: options.refine !== false,\r\n tolerance: parseInt(options.tolerance, 10),\r\n workers: options.workers === \"auto\" ? \"auto\" : options.workers ? parseInt(options.workers, 10) : 1,\r\n method: options.method.toUpperCase(),\r\n headers,\r\n body: options.body,\r\n timeout: options.timeout,\r\n honest: options.honest,\r\n distribution: options.distribution,\r\n onStepStart: (stepNum, load, isRefinement) => {\r\n if (!isSilentStdout) {\r\n const label = isRefinement ? \"Refining\" : \"Testing\";\r\n process.stdout.write(picocolors.dim(` [${stepNum}] ${label} load = ${load}... `));\r\n }\r\n },\r\n onStepComplete: (result) => {\r\n if (!isSilentStdout) {\r\n process.stdout.write(\"\\r\");\r\n console.log(formatStepRow(result, options.mode));\r\n }\r\n },\r\n });\r\n\r\n // Handle machine-readable JSON output\r\n if (typeof options.json === \"string\") {\r\n await fs.writeFile(options.json, formatBreakpointJsonReport(summary), \"utf-8\");\r\n console.log(picocolors.green(`\\nš Saved JSON report to: ${options.json}`));\r\n } else if (options.json === true) {\r\n console.log(formatBreakpointJsonReport(summary));\r\n }\r\n\r\n // Handle machine-readable CSV output\r\n if (typeof options.csv === \"string\") {\r\n await fs.writeFile(options.csv, formatBreakpointCsvReport(summary), \"utf-8\");\r\n console.log(picocolors.green(`\\nš Saved CSV report to: ${options.csv}`));\r\n } else if (options.csv === true) {\r\n console.log(formatBreakpointCsvReport(summary));\r\n }\r\n\r\n if (!isSilentStdout) {\r\n printBreakpointTerminalReport(summary);\r\n }\r\n } catch (err: unknown) {\r\n const errorMsg = err instanceof Error ? err.message : String(err);\r\n console.error(\"\\n\" + picocolors.red(`ā LoadScope breakpoint execution failed: ${errorMsg}\\n`));\r\n process.exit(1);\r\n }\r\n });\r\n\r\n// Subcommand: scenario <file>\r\nprogram\r\n .command(\"scenario <file>\")\r\n .description(\"Execute a multi-step user journey scenario from a YAML or JSON definition\")\r\n .option(\"-c, --concurrency <number>\", \"Override scenario concurrency\")\r\n .option(\"-d, --duration <duration>\", \"Override scenario duration (e.g. 10s, 1m)\")\r\n .option(\"--timeout <duration>\", \"Override request timeout\")\r\n .option(\"--json [path]\", \"Export machine-readable JSON scenario summary\")\r\n .option(\"--html [path]\", \"Generate interactive HTML report for scenario\")\r\n .option(\"--pdf <path>\", \"Generate vector PDF report for scenario\")\r\n .action(async (file, options) => {\r\n try {\r\n const isSilentStdout = options.json === true;\r\n if (!isSilentStdout) {\r\n console.log(picocolors.cyan(`\\nš Initializing LoadScope Scenario Runner: ${picocolors.bold(file)}...`));\r\n }\r\n\r\n const definition = parseScenario(file);\r\n const summary = await runScenario(definition, {\r\n concurrency: options.concurrency ? parseInt(options.concurrency, 10) : undefined,\r\n duration: options.duration,\r\n timeout: options.timeout,\r\n });\r\n\r\n if (typeof options.json === \"string\") {\r\n await fs.writeFile(options.json, JSON.stringify(summary, null, 2), \"utf-8\");\r\n console.log(picocolors.green(`\\nš Saved JSON report to: ${options.json}`));\r\n } else if (options.json === true) {\r\n console.log(JSON.stringify(summary, null, 2));\r\n }\r\n\r\n if (options.html) {\r\n const htmlPath = typeof options.html === \"string\" ? options.html : \"loadscope-scenario-report.html\";\r\n const resolved = await generateHtmlReport(summary.overallSummary, htmlPath);\r\n if (!isSilentStdout) {\r\n console.log(picocolors.green(`\\nš Generated interactive HTML report: ${resolved}`));\r\n }\r\n }\r\n\r\n if (options.pdf) {\r\n const resolvedPdf = await generatePdfReport(summary.overallSummary, options.pdf);\r\n if (!isSilentStdout) {\r\n console.log(picocolors.green(`\\nš Generated vector PDF report: ${resolvedPdf}`));\r\n }\r\n }\r\n\r\n if (!isSilentStdout) {\r\n printScenarioTerminalReport(summary);\r\n }\r\n } catch (err: unknown) {\r\n const errorMsg = err instanceof Error ? err.message : String(err);\r\n console.error(\"\\n\" + picocolors.red(`ā LoadScope scenario execution failed: ${errorMsg}\\n`));\r\n process.exit(1);\r\n }\r\n });\r\n\r\n// Subcommand: compare <current> <baseline>\r\nprogram\r\n .command(\"compare <current> <baseline>\")\r\n .description(\"Compare two performance JSON reports against regression tolerances\")\r\n .option(\"--max-p95-increase <pct>\", \"Allowable percentage increase in p95 latency\", \"10\")\r\n .option(\"--max-rps-drop <pct>\", \"Allowable percentage drop in throughput\", \"5\")\r\n .option(\"--max-p99-increase <pct>\", \"Allowable percentage increase in p99 latency\")\r\n .option(\"--max-errors <pct>\", \"Max error rate percentage before regression (e.g. 1.0)\", \"1.0\")\r\n .option(\"--json [path]\", \"Export machine-readable comparison JSON (to path or stdout)\")\r\n .option(\"--csv [path]\", \"Export machine-readable comparison CSV (to path or stdout)\")\r\n .action(async (currentPath, baselinePath, options) => {\r\n try {\r\n const isSilentStdout = options.json === true || options.csv === true;\r\n\r\n const currentRaw = JSON.parse(await fs.readFile(currentPath, \"utf-8\"));\r\n const baselineRaw = JSON.parse(await fs.readFile(baselinePath, \"utf-8\"));\r\n\r\n const comparison = compareRuns(currentRaw, baselineRaw, {\r\n maxP95IncreasePct: parseFloat(options.maxP95Increase),\r\n maxRpsDropPct: parseFloat(options.maxRpsDrop),\r\n maxP99IncreasePct: options.maxP99Increase ? parseFloat(options.maxP99Increase) : undefined,\r\n maxErrorRate: options.maxErrors ? parseFloat(options.maxErrors) / 100 : 0.01,\r\n });\r\n\r\n if (typeof options.json === \"string\") {\r\n await fs.writeFile(options.json, formatComparisonJsonReport(comparison), \"utf-8\");\r\n console.log(picocolors.green(`\\nš Saved comparison JSON to: ${options.json}`));\r\n } else if (options.json === true) {\r\n console.log(formatComparisonJsonReport(comparison));\r\n }\r\n\r\n if (typeof options.csv === \"string\") {\r\n await fs.writeFile(options.csv, formatComparisonCsvReport(comparison), \"utf-8\");\r\n console.log(picocolors.green(`\\nš Saved comparison CSV to: ${options.csv}`));\r\n } else if (options.csv === true) {\r\n console.log(formatComparisonCsvReport(comparison));\r\n }\r\n\r\n if (!isSilentStdout) {\r\n printComparisonTerminalReport(comparison);\r\n }\r\n\r\n await writeGithubStepSummary(comparison);\r\n\r\n if (!comparison.passed) {\r\n process.exit(1);\r\n }\r\n } catch (err: unknown) {\r\n const errorMsg = err instanceof Error ? err.message : String(err);\r\n console.error(\"\\n\" + picocolors.red(`ā LoadScope compare failed: ${errorMsg}\\n`));\r\n process.exit(1);\r\n }\r\n });\r\n\r\n// Root default command: loadscope [options]\r\nprogram\r\n .option(\"-u, --url <url>\", \"Target URL to test (e.g. http://localhost:3000)\")\r\n .option(\"-c, --concurrency <number>\", \"Number of concurrent connections\", \"10\")\r\n .option(\"-d, --duration <duration>\", \"Test duration (e.g. 10s, 1m, 500ms)\", \"10s\")\r\n .option(\"-m, --method <method>\", \"HTTP method (GET, POST, PUT, DELETE)\", \"GET\")\r\n .option(\"-H, --header <headers...>\", \"Custom HTTP header(s) (e.g. -H 'Authorization: Bearer token')\")\r\n .option(\"-b, --body <body>\", \"HTTP request body\")\r\n .option(\"-r, --rate <number>\", \"Requests per second (open arrival model)\")\r\n .option(\"--distribution <distribution>\", \"Arrival process distribution ('uniform' or 'poisson')\", \"uniform\")\r\n .option(\"--honest\", \"Enable Gil Tene Coordinated Omission correction\")\r\n .option(\"--ttfb\", \"Track Time-To-First-Byte (TTFB) latency breakdown\")\r\n .option(\"-w, --warmup <duration>\", \"Warmup duration before recording metrics (e.g. 2s)\")\r\n .option(\"-W, --workers <number|auto>\", \"Number of worker threads for multi-core scaling (default: 1)\")\r\n .option(\"--timeout <duration>\", \"Per-request timeout (e.g. 5s, 500ms)\", \"10s\")\r\n .option(\"--ramp-up <duration>\", \"Ramp-up duration to stagger worker starts (e.g. 3s)\")\r\n .option(\"--mode <mode>\", \"Execution model ('closed' or 'open')\")\r\n .option(\"--dashboard\", \"Enable real-time interactive terminal dashboard\", true)\r\n .option(\"--no-dashboard\", \"Disable real-time interactive terminal dashboard\")\r\n .option(\"--break\", \"Run in breaking-point finder mode\")\r\n .option(\"--start <number>\", \"Initial load for breaking-point mode\", \"10\")\r\n .option(\"--max <number>\", \"Maximum ceiling load for breaking-point mode\", \"100\")\r\n .option(\"--step <number>\", \"Step increment for breaking-point mode\", \"10\")\r\n .option(\"--step-duration <duration>\", \"Duration per step for breaking-point mode\", \"5s\")\r\n .option(\"--max-p95 <ms>\", \"Max p95 latency before breach (ms)\", \"300\")\r\n .option(\"--max-p99 <ms>\", \"Max p99 latency before breach (ms)\")\r\n .option(\"--max-errors <pct>\", \"Max error rate percentage before breach (e.g. 1.0)\", \"1.0\")\r\n .option(\"--baseline <path>\", \"Compare test results against a baseline JSON file and fail on regression\")\r\n .option(\"--save-baseline [path]\", \"Save the current test run as a baseline file (default: loadscope-baseline.json)\")\r\n .option(\"--max-p95-increase <pct>\", \"Allowable percentage increase in p95 latency for baseline comparison\", \"10\")\r\n .option(\"--max-rps-drop <pct>\", \"Allowable percentage drop in throughput for baseline comparison\", \"5\")\r\n .option(\"--max-p99-increase <pct>\", \"Allowable percentage increase in p99 latency for baseline comparison\")\r\n .option(\"--json [path]\", \"Export machine-readable JSON summary report (to path or stdout)\")\r\n .option(\"--csv [path]\", \"Export machine-readable CSV summary and time-series report (to path or stdout)\")\r\n .option(\"--html [path]\", \"Generate a self-contained interactive HTML report (default: loadscope-report.html)\")\r\n .option(\"--pdf <path>\", \"Generate a PDF report at the specified path\")\r\n .option(\"--ai-report <path>\", \"Generate a PDF report including AI analysis (requires LLM_API_KEY)\")\r\n .option(\"--ai-dry-run\", \"Preview prompt that would be sent to LLM without calling API\")\r\n .action(async (options) => {\r\n if (!options.url) {\r\n program.help();\r\n return;\r\n }\r\n\r\n try {\r\n const isSilentStdout = options.json === true || options.csv === true;\r\n\r\n // Check if user requested --break flag on the root command\r\n if (options.break) {\r\n const headers = options.header ? parseHeaders(options.header) : undefined;\r\n const maxErrorRate = parseFloat(options.maxErrors || \"1.0\") / 100;\r\n const summary = await runBreakingPoint({\r\n url: options.url,\r\n mode: (options.mode === \"open\" || options.rate) ? \"rate\" : \"concurrency\",\r\n start: parseInt(options.start, 10),\r\n max: parseInt(options.max, 10),\r\n step: parseInt(options.step, 10),\r\n stepDuration: options.stepDuration,\r\n maxP95: options.maxP95 ? parseFloat(options.maxP95) : undefined,\r\n maxP99: options.maxP99 ? parseFloat(options.maxP99) : undefined,\r\n maxErrorRate,\r\n refine: true,\r\n workers: options.workers === \"auto\" ? \"auto\" : options.workers ? parseInt(options.workers, 10) : 1,\r\n method: options.method.toUpperCase(),\r\n headers,\r\n body: options.body,\r\n timeout: options.timeout,\r\n honest: options.honest,\r\n distribution: options.distribution,\r\n });\r\n\r\n if (typeof options.json === \"string\") {\r\n await fs.writeFile(options.json, formatBreakpointJsonReport(summary), \"utf-8\");\r\n console.log(picocolors.green(`\\nš Saved JSON report to: ${options.json}`));\r\n } else if (options.json === true) {\r\n console.log(formatBreakpointJsonReport(summary));\r\n }\r\n\r\n if (typeof options.csv === \"string\") {\r\n await fs.writeFile(options.csv, formatBreakpointCsvReport(summary), \"utf-8\");\r\n console.log(picocolors.green(`\\nš Saved CSV report to: ${options.csv}`));\r\n } else if (options.csv === true) {\r\n console.log(formatBreakpointCsvReport(summary));\r\n }\r\n\r\n if (!isSilentStdout) {\r\n printBreakpointTerminalReport(summary);\r\n }\r\n return;\r\n }\r\n\r\n if (!isSilentStdout) {\r\n console.log(picocolors.cyan(`\\nš Initializing LoadScope runner against ${picocolors.bold(options.url)}...`));\r\n const modeDesc = options.rate || options.mode === \"open\"\r\n ? `Open (${options.distribution.toUpperCase()} ${options.rate ?? 50} req/s)`\r\n : `Closed (${options.concurrency} conn)`;\r\n const workersDesc = options.workers ? ` | Workers: ${options.workers}` : \"\";\r\n const honestDesc = options.honest ? \" | Honest CO: ON\" : \"\";\r\n console.log(\r\n picocolors.dim(\r\n ` Mode: ${modeDesc}${workersDesc}${honestDesc} | Duration: ${options.duration} | Method: ${options.method.toUpperCase()}`\r\n )\r\n );\r\n }\r\n\r\n const headers = options.header ? parseHeaders(options.header) : undefined;\r\n\r\n const summary = await run({\r\n url: options.url,\r\n concurrency: parseInt(options.concurrency, 10),\r\n duration: options.duration,\r\n method: options.method.toUpperCase(),\r\n headers,\r\n body: options.body,\r\n rate: options.rate ? parseFloat(options.rate) : undefined,\r\n distribution: options.distribution,\r\n honest: options.honest,\r\n ttfb: options.ttfb,\r\n warmup: options.warmup,\r\n timeout: options.timeout,\r\n rampUp: options.rampUp,\r\n mode: options.mode,\r\n workers: options.workers === \"auto\" ? \"auto\" : options.workers ? parseInt(options.workers, 10) : 1,\r\n dashboard: isSilentStdout ? false : options.dashboard,\r\n json: options.json,\r\n csv: options.csv,\r\n pdf: options.pdf,\r\n aiReport: options.aiReport,\r\n aiDryRun: options.aiDryRun,\r\n });\r\n\r\n // Handle machine-readable JSON output\r\n if (typeof options.json === \"string\") {\r\n await fs.writeFile(options.json, formatJsonReport(summary), \"utf-8\");\r\n console.log(picocolors.green(`\\nš Saved JSON report to: ${options.json}`));\r\n } else if (options.json === true) {\r\n console.log(formatJsonReport(summary));\r\n }\r\n\r\n // Handle machine-readable CSV output\r\n if (typeof options.csv === \"string\") {\r\n await fs.writeFile(options.csv, formatCsvReport(summary), \"utf-8\");\r\n console.log(picocolors.green(`\\nš Saved CSV report to: ${options.csv}`));\r\n } else if (options.csv === true) {\r\n console.log(formatCsvReport(summary));\r\n }\r\n\r\n // Default terminal report when not piping raw JSON/CSV to stdout\r\n if (!isSilentStdout) {\r\n printTerminalReport(summary);\r\n }\r\n\r\n // Phase 6: AI Dry Run\r\n if (options.aiDryRun) {\r\n const prompt = buildAiDiagnosticPrompt(summary);\r\n console.log(picocolors.cyan(\"\\nš¤ LoadScope AI Diagnostic Prompt Preview (--ai-dry-run):\"));\r\n console.log(picocolors.dim(\"ā\".repeat(70)));\r\n console.log(prompt);\r\n console.log(picocolors.dim(\"ā\".repeat(70)));\r\n }\r\n\r\n // Phase 6: PDF Report and AI Report Generation\r\n if (options.pdf || options.aiReport) {\r\n const pdfPath = typeof options.aiReport === \"string\" ? options.aiReport : options.pdf;\r\n let aiAnalysis: string | undefined;\r\n\r\n if (options.aiReport) {\r\n if (!isSilentStdout) {\r\n console.log(picocolors.cyan(\"\\nš¤ Generating AI Performance Diagnostic...\"));\r\n }\r\n aiAnalysis = await fetchAiDiagnostic(summary);\r\n }\r\n\r\n const resolvedPdf = await generatePdfReport(summary, pdfPath, aiAnalysis);\r\n if (!isSilentStdout) {\r\n console.log(picocolors.green(`\\nš Generated vector PDF report: ${resolvedPdf}`));\r\n }\r\n }\r\n\r\n // Phase 7: Interactive HTML Report Generation\r\n if (options.html) {\r\n const htmlPath = typeof options.html === \"string\" ? options.html : \"loadscope-report.html\";\r\n const resolvedHtml = await generateHtmlReport(summary, htmlPath);\r\n if (!isSilentStdout) {\r\n console.log(picocolors.green(`\\nš Generated interactive HTML report: ${resolvedHtml}`));\r\n }\r\n }\r\n\r\n // Phase 5: Baseline Saving\r\n if (options.saveBaseline) {\r\n const baselinePath =\r\n typeof options.saveBaseline === \"string\"\r\n ? options.saveBaseline\r\n : \"loadscope-baseline.json\";\r\n const savedTo = await saveBaseline(summary, baselinePath, {\r\n concurrency: parseInt(options.concurrency, 10),\r\n rate: options.rate ? parseFloat(options.rate) : undefined,\r\n });\r\n if (!isSilentStdout) {\r\n console.log(picocolors.green(`\\nš¾ Saved performance baseline to: ${savedTo}`));\r\n }\r\n }\r\n\r\n // Phase 5: CI Regression Gate Comparison\r\n if (options.baseline) {\r\n const baseline = await loadBaseline(options.baseline);\r\n const comparison = compareRuns(summary, baseline, {\r\n maxP95IncreasePct: parseFloat(options.maxP95Increase || \"10\"),\r\n maxRpsDropPct: parseFloat(options.maxRpsDrop || \"5\"),\r\n maxP99IncreasePct: options.maxP99Increase ? parseFloat(options.maxP99Increase) : undefined,\r\n maxErrorRate: options.maxErrors ? parseFloat(options.maxErrors) / 100 : 0.01,\r\n });\r\n\r\n if (!isSilentStdout) {\r\n printComparisonTerminalReport(comparison);\r\n }\r\n\r\n await writeGithubStepSummary(comparison);\r\n\r\n if (!comparison.passed) {\r\n process.exit(1);\r\n }\r\n }\r\n } catch (err: unknown) {\r\n const errorMsg = err instanceof Error ? err.message : String(err);\r\n console.error(\"\\n\" + picocolors.red(`ā LoadScope execution failed: ${errorMsg}\\n`));\r\n process.exit(1);\r\n }\r\n });\r\n\r\nprogram.parse(process.argv);\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,SAAS,eAAe;AACxB,OAAO,gBAAgB;AACvB,OAAO,QAAQ;AA6Bf,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,WAAW,EAChB,YAAY,uFAAuF,EACnG,QAAQ,OAAO;AAGlB,QACG,QAAQ,aAAa,EACrB,MAAM,YAAY,EAClB,YAAY,iEAAiE,EAC7E,OAAO,oBAAoB,sCAAsC,IAAI,EACrE,OAAO,kBAAkB,gCAAgC,KAAK,EAC9D,OAAO,mBAAmB,kBAAkB,IAAI,EAChD,OAAO,8BAA8B,qCAAqC,IAAI,EAC9E,OAAO,kBAAkB,sCAAsC,KAAK,EACpE,OAAO,kBAAkB,oCAAoC,EAC7D,OAAO,sBAAsB,sDAAsD,KAAK,EACxF,OAAO,qBAAqB,sCAAsC,aAAa,EAC/E,OAAO,eAAe,kCAAkC,EACxD,OAAO,wBAAwB,qCAAqC,GAAG,EACvE,OAAO,+BAA+B,sBAAsB,EAC5D,OAAO,6BAA6B,uBAAuB,EAC3D,OAAO,qBAAqB,mBAAmB,EAC/C,OAAO,qBAAqB,iCAAiC,KAAK,EAClE,OAAO,wBAAwB,mBAAmB,KAAK,EACvD,OAAO,YAAY,mDAAmD,IAAI,EAC1E,OAAO,iCAAiC,4CAA4C,SAAS,EAC7F,OAAO,iBAAiB,iEAAiE,EACzF,OAAO,gBAAgB,wDAAwD,EAC/E,OAAO,OAAO,KAAK,YAAY;AAC9B,MAAI;AACF,UAAM,iBAAiB,QAAQ,SAAS,QAAQ,QAAQ,QAAQ;AAEhE,QAAI,CAAC,gBAAgB;AACnB,cAAQ;AAAA,QACN,WAAW;AAAA,UACT;AAAA,iEAA6D,WAAW,KAAK,GAAG,CAAC;AAAA,QACnF;AAAA,MACF;AACA,cAAQ;AAAA,QACN,WAAW;AAAA,UACT,YAAY,QAAQ,KAAK,WAAM,QAAQ,GAAG,WAAW,QAAQ,IAAI,sBAAsB,QAAQ,YAAY,eAAe,QAAQ,MAAM,oBAAoB,QAAQ,SAAS;AAAA,QAC/K;AAAA,MACF;AACA,cAAQ,IAAI,EAAE;AAAA,IAChB;AAEA,UAAM,UAAU,QAAQ,SAAS,aAAa,QAAQ,MAAM,IAAI;AAChE,UAAM,eAAe,WAAW,QAAQ,SAAS,IAAI;AAErD,UAAM,UAAU,MAAM,iBAAiB;AAAA,MACrC;AAAA,MACA,MAAM,QAAQ;AAAA,MACd,OAAO,SAAS,QAAQ,OAAO,EAAE;AAAA,MACjC,KAAK,SAAS,QAAQ,KAAK,EAAE;AAAA,MAC7B,MAAM,SAAS,QAAQ,MAAM,EAAE;AAAA,MAC/B,cAAc,QAAQ;AAAA,MACtB,QAAQ,QAAQ,SAAS,WAAW,QAAQ,MAAM,IAAI;AAAA,MACtD,QAAQ,QAAQ,SAAS,WAAW,QAAQ,MAAM,IAAI;AAAA,MACtD;AAAA,MACA,QAAQ,QAAQ,WAAW;AAAA,MAC3B,WAAW,SAAS,QAAQ,WAAW,EAAE;AAAA,MACzC,SAAS,QAAQ,YAAY,SAAS,SAAS,QAAQ,UAAU,SAAS,QAAQ,SAAS,EAAE,IAAI;AAAA,MACjG,QAAQ,QAAQ,OAAO,YAAY;AAAA,MACnC;AAAA,MACA,MAAM,QAAQ;AAAA,MACd,SAAS,QAAQ;AAAA,MACjB,QAAQ,QAAQ;AAAA,MAChB,cAAc,QAAQ;AAAA,MACtB,aAAa,CAAC,SAAS,MAAM,iBAAiB;AAC5C,YAAI,CAAC,gBAAgB;AACnB,gBAAM,QAAQ,eAAe,aAAa;AAC1C,kBAAQ,OAAO,MAAM,WAAW,IAAI,OAAO,OAAO,KAAK,KAAK,WAAW,IAAI,MAAM,CAAC;AAAA,QACpF;AAAA,MACF;AAAA,MACA,gBAAgB,CAAC,WAAW;AAC1B,YAAI,CAAC,gBAAgB;AACnB,kBAAQ,OAAO,MAAM,IAAI;AACzB,kBAAQ,IAAI,cAAc,QAAQ,QAAQ,IAAI,CAAC;AAAA,QACjD;AAAA,MACF;AAAA,IACF,CAAC;AAGD,QAAI,OAAO,QAAQ,SAAS,UAAU;AACpC,YAAM,GAAG,UAAU,QAAQ,MAAM,2BAA2B,OAAO,GAAG,OAAO;AAC7E,cAAQ,IAAI,WAAW,MAAM;AAAA,kCAA8B,QAAQ,IAAI,EAAE,CAAC;AAAA,IAC5E,WAAW,QAAQ,SAAS,MAAM;AAChC,cAAQ,IAAI,2BAA2B,OAAO,CAAC;AAAA,IACjD;AAGA,QAAI,OAAO,QAAQ,QAAQ,UAAU;AACnC,YAAM,GAAG,UAAU,QAAQ,KAAK,0BAA0B,OAAO,GAAG,OAAO;AAC3E,cAAQ,IAAI,WAAW,MAAM;AAAA,iCAA6B,QAAQ,GAAG,EAAE,CAAC;AAAA,IAC1E,WAAW,QAAQ,QAAQ,MAAM;AAC/B,cAAQ,IAAI,0BAA0B,OAAO,CAAC;AAAA,IAChD;AAEA,QAAI,CAAC,gBAAgB;AACnB,oCAA8B,OAAO;AAAA,IACvC;AAAA,EACF,SAAS,KAAc;AACrB,UAAM,WAAW,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAChE,YAAQ,MAAM,OAAO,WAAW,IAAI,iDAA4C,QAAQ;AAAA,CAAI,CAAC;AAC7F,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAGH,QACG,QAAQ,iBAAiB,EACzB,YAAY,2EAA2E,EACvF,OAAO,8BAA8B,+BAA+B,EACpE,OAAO,6BAA6B,2CAA2C,EAC/E,OAAO,wBAAwB,0BAA0B,EACzD,OAAO,iBAAiB,+CAA+C,EACvE,OAAO,iBAAiB,+CAA+C,EACvE,OAAO,gBAAgB,yCAAyC,EAChE,OAAO,OAAO,MAAM,YAAY;AAC/B,MAAI;AACF,UAAM,iBAAiB,QAAQ,SAAS;AACxC,QAAI,CAAC,gBAAgB;AACnB,cAAQ,IAAI,WAAW,KAAK;AAAA,oDAAgD,WAAW,KAAK,IAAI,CAAC,KAAK,CAAC;AAAA,IACzG;AAEA,UAAM,aAAa,cAAc,IAAI;AACrC,UAAM,UAAU,MAAM,YAAY,YAAY;AAAA,MAC5C,aAAa,QAAQ,cAAc,SAAS,QAAQ,aAAa,EAAE,IAAI;AAAA,MACvE,UAAU,QAAQ;AAAA,MAClB,SAAS,QAAQ;AAAA,IACnB,CAAC;AAED,QAAI,OAAO,QAAQ,SAAS,UAAU;AACpC,YAAM,GAAG,UAAU,QAAQ,MAAM,KAAK,UAAU,SAAS,MAAM,CAAC,GAAG,OAAO;AAC1E,cAAQ,IAAI,WAAW,MAAM;AAAA,kCAA8B,QAAQ,IAAI,EAAE,CAAC;AAAA,IAC5E,WAAW,QAAQ,SAAS,MAAM;AAChC,cAAQ,IAAI,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AAAA,IAC9C;AAEA,QAAI,QAAQ,MAAM;AAChB,YAAM,WAAW,OAAO,QAAQ,SAAS,WAAW,QAAQ,OAAO;AACnE,YAAM,WAAW,MAAM,mBAAmB,QAAQ,gBAAgB,QAAQ;AAC1E,UAAI,CAAC,gBAAgB;AACnB,gBAAQ,IAAI,WAAW,MAAM;AAAA,+CAA2C,QAAQ,EAAE,CAAC;AAAA,MACrF;AAAA,IACF;AAEA,QAAI,QAAQ,KAAK;AACf,YAAM,cAAc,MAAM,kBAAkB,QAAQ,gBAAgB,QAAQ,GAAG;AAC/E,UAAI,CAAC,gBAAgB;AACnB,gBAAQ,IAAI,WAAW,MAAM;AAAA,yCAAqC,WAAW,EAAE,CAAC;AAAA,MAClF;AAAA,IACF;AAEA,QAAI,CAAC,gBAAgB;AACnB,kCAA4B,OAAO;AAAA,IACrC;AAAA,EACF,SAAS,KAAc;AACrB,UAAM,WAAW,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAChE,YAAQ,MAAM,OAAO,WAAW,IAAI,+CAA0C,QAAQ;AAAA,CAAI,CAAC;AAC3F,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAGH,QACG,QAAQ,8BAA8B,EACtC,YAAY,oEAAoE,EAChF,OAAO,4BAA4B,gDAAgD,IAAI,EACvF,OAAO,wBAAwB,2CAA2C,GAAG,EAC7E,OAAO,4BAA4B,8CAA8C,EACjF,OAAO,sBAAsB,0DAA0D,KAAK,EAC5F,OAAO,iBAAiB,6DAA6D,EACrF,OAAO,gBAAgB,4DAA4D,EACnF,OAAO,OAAO,aAAa,cAAc,YAAY;AACpD,MAAI;AACF,UAAM,iBAAiB,QAAQ,SAAS,QAAQ,QAAQ,QAAQ;AAEhE,UAAM,aAAa,KAAK,MAAM,MAAM,GAAG,SAAS,aAAa,OAAO,CAAC;AACrE,UAAM,cAAc,KAAK,MAAM,MAAM,GAAG,SAAS,cAAc,OAAO,CAAC;AAEvE,UAAM,aAAa,YAAY,YAAY,aAAa;AAAA,MACtD,mBAAmB,WAAW,QAAQ,cAAc;AAAA,MACpD,eAAe,WAAW,QAAQ,UAAU;AAAA,MAC5C,mBAAmB,QAAQ,iBAAiB,WAAW,QAAQ,cAAc,IAAI;AAAA,MACjF,cAAc,QAAQ,YAAY,WAAW,QAAQ,SAAS,IAAI,MAAM;AAAA,IAC1E,CAAC;AAED,QAAI,OAAO,QAAQ,SAAS,UAAU;AACpC,YAAM,GAAG,UAAU,QAAQ,MAAM,2BAA2B,UAAU,GAAG,OAAO;AAChF,cAAQ,IAAI,WAAW,MAAM;AAAA,sCAAkC,QAAQ,IAAI,EAAE,CAAC;AAAA,IAChF,WAAW,QAAQ,SAAS,MAAM;AAChC,cAAQ,IAAI,2BAA2B,UAAU,CAAC;AAAA,IACpD;AAEA,QAAI,OAAO,QAAQ,QAAQ,UAAU;AACnC,YAAM,GAAG,UAAU,QAAQ,KAAK,0BAA0B,UAAU,GAAG,OAAO;AAC9E,cAAQ,IAAI,WAAW,MAAM;AAAA,qCAAiC,QAAQ,GAAG,EAAE,CAAC;AAAA,IAC9E,WAAW,QAAQ,QAAQ,MAAM;AAC/B,cAAQ,IAAI,0BAA0B,UAAU,CAAC;AAAA,IACnD;AAEA,QAAI,CAAC,gBAAgB;AACnB,oCAA8B,UAAU;AAAA,IAC1C;AAEA,UAAM,uBAAuB,UAAU;AAEvC,QAAI,CAAC,WAAW,QAAQ;AACtB,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF,SAAS,KAAc;AACrB,UAAM,WAAW,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAChE,YAAQ,MAAM,OAAO,WAAW,IAAI,oCAA+B,QAAQ;AAAA,CAAI,CAAC;AAChF,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAGH,QACG,OAAO,mBAAmB,iDAAiD,EAC3E,OAAO,8BAA8B,oCAAoC,IAAI,EAC7E,OAAO,6BAA6B,uCAAuC,KAAK,EAChF,OAAO,yBAAyB,wCAAwC,KAAK,EAC7E,OAAO,6BAA6B,+DAA+D,EACnG,OAAO,qBAAqB,mBAAmB,EAC/C,OAAO,uBAAuB,0CAA0C,EACxE,OAAO,iCAAiC,yDAAyD,SAAS,EAC1G,OAAO,YAAY,iDAAiD,EACpE,OAAO,UAAU,mDAAmD,EACpE,OAAO,2BAA2B,oDAAoD,EACtF,OAAO,+BAA+B,8DAA8D,EACpG,OAAO,wBAAwB,wCAAwC,KAAK,EAC5E,OAAO,wBAAwB,qDAAqD,EACpF,OAAO,iBAAiB,sCAAsC,EAC9D,OAAO,eAAe,mDAAmD,IAAI,EAC7E,OAAO,kBAAkB,kDAAkD,EAC3E,OAAO,WAAW,mCAAmC,EACrD,OAAO,oBAAoB,wCAAwC,IAAI,EACvE,OAAO,kBAAkB,gDAAgD,KAAK,EAC9E,OAAO,mBAAmB,0CAA0C,IAAI,EACxE,OAAO,8BAA8B,6CAA6C,IAAI,EACtF,OAAO,kBAAkB,sCAAsC,KAAK,EACpE,OAAO,kBAAkB,oCAAoC,EAC7D,OAAO,sBAAsB,sDAAsD,KAAK,EACxF,OAAO,qBAAqB,0EAA0E,EACtG,OAAO,0BAA0B,iFAAiF,EAClH,OAAO,4BAA4B,wEAAwE,IAAI,EAC/G,OAAO,wBAAwB,mEAAmE,GAAG,EACrG,OAAO,4BAA4B,sEAAsE,EACzG,OAAO,iBAAiB,iEAAiE,EACzF,OAAO,gBAAgB,gFAAgF,EACvG,OAAO,iBAAiB,oFAAoF,EAC5G,OAAO,gBAAgB,6CAA6C,EACpE,OAAO,sBAAsB,oEAAoE,EACjG,OAAO,gBAAgB,8DAA8D,EACrF,OAAO,OAAO,YAAY;AACzB,MAAI,CAAC,QAAQ,KAAK;AAChB,YAAQ,KAAK;AACb;AAAA,EACF;AAEA,MAAI;AACF,UAAM,iBAAiB,QAAQ,SAAS,QAAQ,QAAQ,QAAQ;AAGhE,QAAI,QAAQ,OAAO;AACjB,YAAMA,WAAU,QAAQ,SAAS,aAAa,QAAQ,MAAM,IAAI;AAChE,YAAM,eAAe,WAAW,QAAQ,aAAa,KAAK,IAAI;AAC9D,YAAMC,WAAU,MAAM,iBAAiB;AAAA,QACrC,KAAK,QAAQ;AAAA,QACb,MAAO,QAAQ,SAAS,UAAU,QAAQ,OAAQ,SAAS;AAAA,QAC3D,OAAO,SAAS,QAAQ,OAAO,EAAE;AAAA,QACjC,KAAK,SAAS,QAAQ,KAAK,EAAE;AAAA,QAC7B,MAAM,SAAS,QAAQ,MAAM,EAAE;AAAA,QAC/B,cAAc,QAAQ;AAAA,QACtB,QAAQ,QAAQ,SAAS,WAAW,QAAQ,MAAM,IAAI;AAAA,QACtD,QAAQ,QAAQ,SAAS,WAAW,QAAQ,MAAM,IAAI;AAAA,QACtD;AAAA,QACA,QAAQ;AAAA,QACR,SAAS,QAAQ,YAAY,SAAS,SAAS,QAAQ,UAAU,SAAS,QAAQ,SAAS,EAAE,IAAI;AAAA,QACjG,QAAQ,QAAQ,OAAO,YAAY;AAAA,QACnC,SAAAD;AAAA,QACA,MAAM,QAAQ;AAAA,QACd,SAAS,QAAQ;AAAA,QACjB,QAAQ,QAAQ;AAAA,QAChB,cAAc,QAAQ;AAAA,MACxB,CAAC;AAED,UAAI,OAAO,QAAQ,SAAS,UAAU;AACpC,cAAM,GAAG,UAAU,QAAQ,MAAM,2BAA2BC,QAAO,GAAG,OAAO;AAC7E,gBAAQ,IAAI,WAAW,MAAM;AAAA,kCAA8B,QAAQ,IAAI,EAAE,CAAC;AAAA,MAC5E,WAAW,QAAQ,SAAS,MAAM;AAChC,gBAAQ,IAAI,2BAA2BA,QAAO,CAAC;AAAA,MACjD;AAEA,UAAI,OAAO,QAAQ,QAAQ,UAAU;AACnC,cAAM,GAAG,UAAU,QAAQ,KAAK,0BAA0BA,QAAO,GAAG,OAAO;AAC3E,gBAAQ,IAAI,WAAW,MAAM;AAAA,iCAA6B,QAAQ,GAAG,EAAE,CAAC;AAAA,MAC1E,WAAW,QAAQ,QAAQ,MAAM;AAC/B,gBAAQ,IAAI,0BAA0BA,QAAO,CAAC;AAAA,MAChD;AAEA,UAAI,CAAC,gBAAgB;AACnB,sCAA8BA,QAAO;AAAA,MACvC;AACA;AAAA,IACF;AAEA,QAAI,CAAC,gBAAgB;AACnB,cAAQ,IAAI,WAAW,KAAK;AAAA,kDAA8C,WAAW,KAAK,QAAQ,GAAG,CAAC,KAAK,CAAC;AAC5G,YAAM,WAAW,QAAQ,QAAQ,QAAQ,SAAS,SAC9C,SAAS,QAAQ,aAAa,YAAY,CAAC,IAAI,QAAQ,QAAQ,EAAE,YACjE,WAAW,QAAQ,WAAW;AAClC,YAAM,cAAc,QAAQ,UAAU,eAAe,QAAQ,OAAO,KAAK;AACzE,YAAM,aAAa,QAAQ,SAAS,qBAAqB;AACzD,cAAQ;AAAA,QACN,WAAW;AAAA,UACT,YAAY,QAAQ,GAAG,WAAW,GAAG,UAAU,gBAAgB,QAAQ,QAAQ,cAAc,QAAQ,OAAO,YAAY,CAAC;AAAA,QAC3H;AAAA,MACF;AAAA,IACF;AAEA,UAAM,UAAU,QAAQ,SAAS,aAAa,QAAQ,MAAM,IAAI;AAEhE,UAAM,UAAU,MAAM,IAAI;AAAA,MACxB,KAAK,QAAQ;AAAA,MACb,aAAa,SAAS,QAAQ,aAAa,EAAE;AAAA,MAC7C,UAAU,QAAQ;AAAA,MAClB,QAAQ,QAAQ,OAAO,YAAY;AAAA,MACnC;AAAA,MACA,MAAM,QAAQ;AAAA,MACd,MAAM,QAAQ,OAAO,WAAW,QAAQ,IAAI,IAAI;AAAA,MAChD,cAAc,QAAQ;AAAA,MACtB,QAAQ,QAAQ;AAAA,MAChB,MAAM,QAAQ;AAAA,MACd,QAAQ,QAAQ;AAAA,MAChB,SAAS,QAAQ;AAAA,MACjB,QAAQ,QAAQ;AAAA,MAChB,MAAM,QAAQ;AAAA,MACd,SAAS,QAAQ,YAAY,SAAS,SAAS,QAAQ,UAAU,SAAS,QAAQ,SAAS,EAAE,IAAI;AAAA,MACjG,WAAW,iBAAiB,QAAQ,QAAQ;AAAA,MAC5C,MAAM,QAAQ;AAAA,MACd,KAAK,QAAQ;AAAA,MACb,KAAK,QAAQ;AAAA,MACb,UAAU,QAAQ;AAAA,MAClB,UAAU,QAAQ;AAAA,IACpB,CAAC;AAGD,QAAI,OAAO,QAAQ,SAAS,UAAU;AACpC,YAAM,GAAG,UAAU,QAAQ,MAAM,iBAAiB,OAAO,GAAG,OAAO;AACnE,cAAQ,IAAI,WAAW,MAAM;AAAA,kCAA8B,QAAQ,IAAI,EAAE,CAAC;AAAA,IAC5E,WAAW,QAAQ,SAAS,MAAM;AAChC,cAAQ,IAAI,iBAAiB,OAAO,CAAC;AAAA,IACvC;AAGA,QAAI,OAAO,QAAQ,QAAQ,UAAU;AACnC,YAAM,GAAG,UAAU,QAAQ,KAAK,gBAAgB,OAAO,GAAG,OAAO;AACjE,cAAQ,IAAI,WAAW,MAAM;AAAA,iCAA6B,QAAQ,GAAG,EAAE,CAAC;AAAA,IAC1E,WAAW,QAAQ,QAAQ,MAAM;AAC/B,cAAQ,IAAI,gBAAgB,OAAO,CAAC;AAAA,IACtC;AAGA,QAAI,CAAC,gBAAgB;AACnB,0BAAoB,OAAO;AAAA,IAC7B;AAGA,QAAI,QAAQ,UAAU;AACpB,YAAM,SAAS,wBAAwB,OAAO;AAC9C,cAAQ,IAAI,WAAW,KAAK,oEAA6D,CAAC;AAC1F,cAAQ,IAAI,WAAW,IAAI,SAAI,OAAO,EAAE,CAAC,CAAC;AAC1C,cAAQ,IAAI,MAAM;AAClB,cAAQ,IAAI,WAAW,IAAI,SAAI,OAAO,EAAE,CAAC,CAAC;AAAA,IAC5C;AAGA,QAAI,QAAQ,OAAO,QAAQ,UAAU;AACnC,YAAM,UAAU,OAAO,QAAQ,aAAa,WAAW,QAAQ,WAAW,QAAQ;AAClF,UAAI;AAEJ,UAAI,QAAQ,UAAU;AACpB,YAAI,CAAC,gBAAgB;AACnB,kBAAQ,IAAI,WAAW,KAAK,qDAA8C,CAAC;AAAA,QAC7E;AACA,qBAAa,MAAM,kBAAkB,OAAO;AAAA,MAC9C;AAEA,YAAM,cAAc,MAAM,kBAAkB,SAAS,SAAS,UAAU;AACxE,UAAI,CAAC,gBAAgB;AACnB,gBAAQ,IAAI,WAAW,MAAM;AAAA,yCAAqC,WAAW,EAAE,CAAC;AAAA,MAClF;AAAA,IACF;AAGA,QAAI,QAAQ,MAAM;AAChB,YAAM,WAAW,OAAO,QAAQ,SAAS,WAAW,QAAQ,OAAO;AACnE,YAAM,eAAe,MAAM,mBAAmB,SAAS,QAAQ;AAC/D,UAAI,CAAC,gBAAgB;AACnB,gBAAQ,IAAI,WAAW,MAAM;AAAA,+CAA2C,YAAY,EAAE,CAAC;AAAA,MACzF;AAAA,IACF;AAGA,QAAI,QAAQ,cAAc;AACxB,YAAM,eACJ,OAAO,QAAQ,iBAAiB,WAC5B,QAAQ,eACR;AACN,YAAM,UAAU,MAAM,aAAa,SAAS,cAAc;AAAA,QACxD,aAAa,SAAS,QAAQ,aAAa,EAAE;AAAA,QAC7C,MAAM,QAAQ,OAAO,WAAW,QAAQ,IAAI,IAAI;AAAA,MAClD,CAAC;AACD,UAAI,CAAC,gBAAgB;AACnB,gBAAQ,IAAI,WAAW,MAAM;AAAA,2CAAuC,OAAO,EAAE,CAAC;AAAA,MAChF;AAAA,IACF;AAGA,QAAI,QAAQ,UAAU;AACpB,YAAM,WAAW,MAAM,aAAa,QAAQ,QAAQ;AACpD,YAAM,aAAa,YAAY,SAAS,UAAU;AAAA,QAChD,mBAAmB,WAAW,QAAQ,kBAAkB,IAAI;AAAA,QAC5D,eAAe,WAAW,QAAQ,cAAc,GAAG;AAAA,QACnD,mBAAmB,QAAQ,iBAAiB,WAAW,QAAQ,cAAc,IAAI;AAAA,QACjF,cAAc,QAAQ,YAAY,WAAW,QAAQ,SAAS,IAAI,MAAM;AAAA,MAC1E,CAAC;AAED,UAAI,CAAC,gBAAgB;AACnB,sCAA8B,UAAU;AAAA,MAC1C;AAEA,YAAM,uBAAuB,UAAU;AAEvC,UAAI,CAAC,WAAW,QAAQ;AACtB,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF;AAAA,EACF,SAAS,KAAc;AACrB,UAAM,WAAW,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAChE,YAAQ,MAAM,OAAO,WAAW,IAAI,sCAAiC,QAAQ;AAAA,CAAI,CAAC;AAClF,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,QAAQ,MAAM,QAAQ,IAAI;","names":["headers","summary"]}
|