Skip to content

Commit 5dd1545

Browse files
pranaygpclaudevercel[bot]
authored
Test and benchmark community worlds against e2e tests (vercel#482)
* Add new github workflow * Enable pull_request trigger for community worlds workflow 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Add community worlds manifest and generation scripts - Add community-worlds.json manifest as single source of truth - Add scripts/generate-community-worlds-workflow.mjs to generate CI workflow - Add scripts/generate-community-worlds-docs.mjs to generate docs section - Update aggregate-benchmarks.js to load community worlds dynamically - Add pnpm generate:community-worlds script - Update docs/deploying/world/index.mdx with community worlds The manifest-based approach allows: - E2E tests to be auto-generated from the manifest - Benchmark aggregation to include community worlds - Docs to stay in sync with tested worlds 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix YAML syntax error - quote strings starting with @ The @ symbol has special meaning in YAML, so package names like @workflow-worlds/turso need to be quoted. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix Redis health-cmd quoting, remove unpublished starter world - Quote health-cmd when it contains spaces (fixes Docker arg parsing) - Remove @workflow-worlds/starter as it's not published to npm 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Add benchmarks and summary job for community worlds - Add build job to share artifacts between benchmark jobs - Add benchmark jobs for Turso, MongoDB, and Redis worlds - Update summary job to show both E2E and benchmark status matrix - Add left border/indent to sidebar child items for visual hierarchy - Update workflow generator to support benchmark generation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Reuse build artifacts for E2E tests E2E jobs now depend on the shared build job and download artifacts instead of rebuilding packages from scratch. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Add Worlds Ecosystem dashboard to docs - Create worlds-manifest.json with official and community worlds - Add aggregate-worlds-data.mjs script for processing E2E and benchmark results - Create WorldsDashboard, WorldCard, and BenchmarkChart components - Add /docs/worlds page showing compatibility status and performance - Include sample data for development The dashboard shows: - E2E test progress per world (pass/fail/skip counts) - Benchmark performance comparison across all worlds - Filter by official vs community worlds 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix CI to output JSON test results and add Jazz world - Update workflow generator to output JSON test results from vitest - Upload E2E results as artifacts for parsing in summary job - Summary job now shows actual pass/fail/skip counts per world - Add Jazz world to worlds-manifest.json (requires external credentials) - Add update-worlds-status.yml workflow to auto-update dashboard data - Update TypeScript types to support null lastRun and metrics 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Refactor community worlds to use reusable workflows Instead of a generated workflow file, integrate community world testing directly into tests.yml and benchmarks.yml using reusable workflows. - Add reusable workflows for E2E tests: e2e-community-world.yml (no services), e2e-community-world-mongodb.yml, e2e-community-world-redis.yml - Add reusable workflows for benchmarks: benchmark-community-world.yml, benchmark-community-world-mongodb.yml, benchmark-community-world-redis.yml - Update tests.yml to call reusable workflows for Turso, MongoDB, Redis - Update benchmarks.yml to include community world benchmarks in summary - Delete generated community-worlds.yml and generator script This approach: - Inherits proper Rust/SWC setup from the main workflows - Keeps all CI in the established patterns - Makes adding new community worlds straightforward 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix benchmark timing file naming for community worlds Add WORKFLOW_BENCH_BACKEND env var support to bench.bench.ts so community world benchmarks generate timing files with the correct backend suffix (e.g., bench-timings-nextjs-turbopack-turso.json instead of -local.json). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Add @workflow-worlds/starter to community worlds test matrix 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Unify worlds manifest and add dynamic GitHub API fetching - Merge community-worlds.json into worlds-manifest.json with type field - Add server-side data fetching from GitHub API for worlds dashboard - Remove static worlds-status.json, fetch CI artifacts dynamically - Update all references to use unified manifest format - Remove obsolete community-worlds.yml and update-worlds-status.yml workflows 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Skip community worlds for non-nextjs-turbopack in benchmark summary Community worlds only run against nextjs-turbopack, so hide the "missing" rows for Express and Nitro frameworks in the benchmark comparison tables. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix stream benchmark detection to check for actual TTFB data The previous check `!== null` incorrectly returned true for undefined, causing all benchmarks to show TTFB columns. Now explicitly checks for a number type. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Hide Worlds Ecosystem page from sidebar The page is still accessible via direct link at /docs/worlds but won't appear in the navigation until it's been further iterated on. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix review comments: trailing newline and division by zero - Add trailing newline when replacing Community Worlds section in docs - Fix division by zero in WorldCard benchmark calculation when metrics is empty 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Consolidate community world workflows with service-type parameter - Create setup-workflow-dev composite action for common setup steps - Add service-type input to benchmark-community-world.yml and e2e-community-world.yml - Use conditional job execution (if: inputs.service-type == 'mongodb') to handle different services - Update benchmarks.yml and tests.yml to pass service-type parameter - Delete redundant workflow files: - benchmark-community-world-mongodb.yml - benchmark-community-world-redis.yml - e2e-community-world-mongodb.yml - e2e-community-world-redis.yml Reduces workflow files from 11 to 7 and eliminates ~500 lines of duplicated YAML. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Generate community world test matrix from worlds-manifest.json Replace hardcoded community world jobs with dynamic matrix generation using scripts/create-community-worlds-matrix.mjs. This allows adding/removing community worlds by editing the manifest instead of multiple workflow files. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Apply suggestion from @vercel[bot] Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> * Add Samples column and separate local/production benchmarks - Add Samples column to all benchmark tables showing iteration count - Separate benchmark results into Local Development and Production sections - Add explanatory context for each section (localhost vs Vercel deployment) - Add GitHub action step summaries to e2e community world tests - Create aggregate-e2e-results.js script for parsing vitest JSON output 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Remove obsolete generate-community-worlds-docs script The Worlds Ecosystem page now fetches from worlds-manifest.json at runtime, making this script unnecessary. The npm script also referenced a non-existent workflow generator script. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Maximize composite action usage and reorganize benchmark output - Update setup-workflow-dev composite action with optional Rust, install-dependencies, and install-args inputs - Update tests.yml to use composite action in unit, e2e-vercel-prod, getTestMatrix, e2e-local-*, and getCommunityWorldsMatrix jobs - Update benchmarks.yml to use composite action in build, benchmark-local, benchmark-postgres, benchmark-vercel, and getCommunityWorldsMatrix jobs - Reorganize benchmark output to group by benchmark test with local/production tables within each benchmark - Remove invalid $schema reference from worlds-manifest.json 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Add beads stealth mode stuff (for personal claude memory - will remove stealth if people want) * Add E2E test results PR comment summary - Add pr-comment-start job to create/update PR comment when tests start - Add artifact uploads to all e2e test jobs (vercel-prod, local-dev, local-prod, local-postgres, windows) - Update e2e-community-world.yml with consistent artifact naming (e2e-community-*) - Add summary job to aggregate all e2e results and update PR comment - Extend aggregate-e2e-results.js with --mode aggregate for multi-job PR summary - Group results by category (Vercel Production, Local Development, etc.) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Add step summaries to all e2e test jobs Add "Generate E2E summary" step to each individual e2e job: - e2e-vercel-prod - e2e-local-dev - e2e-local-prod - e2e-local-postgres - e2e-windows Each job now outputs pass/fail/skip counts to GITHUB_STEP_SUMMARY. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Consolidate community world workflows to single job Replace 3 mutually exclusive jobs (e2e/e2e-mongodb/e2e-redis) with a single job that starts services via docker run when needed. This eliminates the skipped job entries that appear in the GitHub Actions UI. - Use conditional docker run steps instead of services: block - Add health check loops to wait for service readiness - Add cleanup step to stop containers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix extractWorldId to handle community world artifact naming Add handling for `e2e-results-community-{world}` pattern so community world test results are properly extracted (e.g., `e2e-results-community-turso` now correctly extracts `turso` instead of `community-turso`). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
1 parent d399c52 commit 5dd1545

24 files changed

Lines changed: 3014 additions & 141 deletions

.gitattributes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@
22
# Use LF for all test files to ensure cross-platform consistency
33
packages/swc-plugin-workflow/transform/tests/**/*.js text eol=lf
44
packages/swc-plugin-workflow/transform/tests/**/*.stderr text eol=lf
5+
6+
# Use bd merge for beads JSONL files
7+
.beads/issues.jsonl merge=beads
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
name: 'Setup Workflow Dev Environment'
2+
description: 'Setup Node.js, pnpm, and optionally Rust for Workflow development. Note: Checkout must be done before calling this action.'
3+
4+
inputs:
5+
node-version:
6+
description: 'Node.js version to use'
7+
required: false
8+
default: '22.x'
9+
pnpm-version:
10+
description: 'pnpm version to use'
11+
required: false
12+
default: '10.14.0'
13+
setup-rust:
14+
description: 'Whether to setup Rust toolchain'
15+
required: false
16+
default: 'false'
17+
install-dependencies:
18+
description: 'Whether to install dependencies'
19+
required: false
20+
default: 'true'
21+
install-args:
22+
description: 'Additional arguments for pnpm install (e.g., --ignore-scripts)'
23+
required: false
24+
default: ''
25+
build-packages:
26+
description: 'Whether to build packages (excludes workbenches)'
27+
required: false
28+
default: 'true'
29+
30+
runs:
31+
using: 'composite'
32+
steps:
33+
- name: Setup Rust
34+
if: ${{ inputs.setup-rust == 'true' }}
35+
uses: actions-rust-lang/setup-rust-toolchain@v1
36+
with:
37+
toolchain: stable
38+
39+
- name: Setup pnpm
40+
uses: pnpm/action-setup@v3
41+
with:
42+
version: ${{ inputs.pnpm-version }}
43+
44+
- name: Setup Node.js ${{ inputs.node-version }}
45+
uses: actions/setup-node@v4
46+
with:
47+
node-version: ${{ inputs.node-version }}
48+
cache: 'pnpm'
49+
50+
- name: Install Dependencies
51+
if: ${{ inputs.install-dependencies == 'true' }}
52+
shell: bash
53+
run: pnpm install --frozen-lockfile ${{ inputs.install-args }}
54+
55+
- name: Build all packages
56+
if: ${{ inputs.build-packages == 'true' }}
57+
shell: bash
58+
run: pnpm turbo run build --filter='!./workbench/*'

.github/scripts/aggregate-benchmarks.js

Lines changed: 114 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,35 @@ for (let i = 0; i < args.length; i++) {
1717
}
1818
}
1919

20-
// World display config
20+
// World display config - built-in worlds
2121
const worldConfig = {
2222
local: { emoji: '💻', label: 'Local' },
2323
postgres: { emoji: '🐘', label: 'Postgres' },
2424
vercel: { emoji: '▲', label: 'Vercel' },
2525
};
2626

27+
// Load community worlds from manifest and add to worldConfig
28+
const worldsManifestPath = path.join(__dirname, '../../worlds-manifest.json');
29+
if (fs.existsSync(worldsManifestPath)) {
30+
try {
31+
const worldsManifest = JSON.parse(
32+
fs.readFileSync(worldsManifestPath, 'utf-8')
33+
);
34+
for (const world of worldsManifest.worlds || []) {
35+
// Only add community worlds (official ones are already defined above)
36+
if (world.type === 'community') {
37+
worldConfig[world.id] = {
38+
emoji: '🌐',
39+
label: world.name,
40+
community: true,
41+
};
42+
}
43+
}
44+
} catch (e) {
45+
console.error(`Warning: Could not load worlds manifest: ${e.message}`);
46+
}
47+
}
48+
2749
// Framework display config
2850
const frameworkConfig = {
2951
'nextjs-turbopack': { label: 'Next.js (Turbopack)' },
@@ -201,7 +223,9 @@ function getAppsAndBackends(data) {
201223
function isStreamBenchmark(benchData, apps, backends) {
202224
for (const app of apps) {
203225
for (const backend of backends) {
204-
if (benchData[app]?.[backend]?.firstByteTime !== null) {
226+
const firstByteTime = benchData[app]?.[backend]?.firstByteTime;
227+
// Must be a number (not null or undefined) to be a stream benchmark
228+
if (typeof firstByteTime === 'number') {
205229
return true;
206230
}
207231
}
@@ -216,15 +240,24 @@ function renderBenchmarkTable(
216240
baselineBenchData,
217241
apps,
218242
backends,
219-
isStream
243+
isStream,
244+
{ showHeading = true } = {}
220245
) {
221-
console.log(`## ${benchName}\n`);
246+
if (showHeading) {
247+
console.log(`## ${benchName}\n`);
248+
}
222249

223250
// Collect all data points (including missing ones) for all app/backend combinations
224251
const dataPoints = [];
225252
const validDataPoints = [];
226253
for (const app of apps) {
227254
for (const backend of backends) {
255+
// Skip community worlds for non-nextjs-turbopack frameworks (we only test them with nextjs-turbopack)
256+
const isCommunityWorld = worldConfig[backend]?.community === true;
257+
if (isCommunityWorld && app !== 'nextjs-turbopack') {
258+
continue;
259+
}
260+
228261
const metrics = benchData[app]?.[backend];
229262
const baseline = baselineBenchData?.[app]?.[backend] || null;
230263
const dataPoint = { app, backend, metrics: metrics || null, baseline };
@@ -264,17 +297,17 @@ function renderBenchmarkTable(
264297
// Render table - different columns for stream vs regular benchmarks
265298
if (isStream) {
266299
console.log(
267-
'| World | Framework | Workflow Time | TTFB | Wall Time | Overhead | vs Fastest |'
300+
'| World | Framework | Workflow Time | TTFB | Wall Time | Overhead | Samples | vs Fastest |'
268301
);
269302
console.log(
270-
'|:------|:----------|--------------:|-----:|----------:|---------:|-----------:|'
303+
'|:------|:----------|--------------:|-----:|----------:|---------:|--------:|-----------:|'
271304
);
272305
} else {
273306
console.log(
274-
'| World | Framework | Workflow Time | Wall Time | Overhead | vs Fastest |'
307+
'| World | Framework | Workflow Time | Wall Time | Overhead | Samples | vs Fastest |'
275308
);
276309
console.log(
277-
'|:------|:----------|--------------:|----------:|---------:|-----------:|'
310+
'|:------|:----------|--------------:|----------:|---------:|--------:|-----------:|'
278311
);
279312
}
280313

@@ -289,11 +322,11 @@ function renderBenchmarkTable(
289322
if (!metrics) {
290323
if (isStream) {
291324
console.log(
292-
`| ${worldInfo.emoji} ${worldInfo.label} | ${frameworkInfo.label} | ⚠️ _missing_ | - | - | - | - |`
325+
`| ${worldInfo.emoji} ${worldInfo.label} | ${frameworkInfo.label} | ⚠️ _missing_ | - | - | - | - | - |`
293326
);
294327
} else {
295328
console.log(
296-
`| ${worldInfo.emoji} ${worldInfo.label} | ${frameworkInfo.label} | ⚠️ _missing_ | - | - | - |`
329+
`| ${worldInfo.emoji} ${worldInfo.label} | ${frameworkInfo.label} | ⚠️ _missing_ | - | - | - | - |`
297330
);
298331
}
299332
continue;
@@ -326,18 +359,21 @@ function renderBenchmarkTable(
326359
baseline?.firstByteTime
327360
);
328361

362+
// Format samples count
363+
const samplesCount = metrics.samples ?? '-';
364+
329365
const currentTime = metrics.workflowTime ?? metrics.wallTime;
330366
const factor = isFastest
331367
? '1.00x'
332368
: `${(currentTime / fastestTime).toFixed(2)}x`;
333369

334370
if (isStream) {
335371
console.log(
336-
`| ${worldInfo.emoji} ${worldInfo.label} | ${medal}${frameworkInfo.label} | ${workflowTimeSec}s${workflowDelta} | ${firstByteSec}s${ttfbDelta} | ${wallTimeSec}s${wallDelta} | ${overheadSec}s | ${factor} |`
372+
`| ${worldInfo.emoji} ${worldInfo.label} | ${medal}${frameworkInfo.label} | ${workflowTimeSec}s${workflowDelta} | ${firstByteSec}s${ttfbDelta} | ${wallTimeSec}s${wallDelta} | ${overheadSec}s | ${samplesCount} | ${factor} |`
337373
);
338374
} else {
339375
console.log(
340-
`| ${worldInfo.emoji} ${worldInfo.label} | ${medal}${frameworkInfo.label} | ${workflowTimeSec}s${workflowDelta} | ${wallTimeSec}s${wallDelta} | ${overheadSec}s | ${factor} |`
376+
`| ${worldInfo.emoji} ${worldInfo.label} | ${medal}${frameworkInfo.label} | ${workflowTimeSec}s${workflowDelta} | ${wallTimeSec}s${wallDelta} | ${overheadSec}s | ${samplesCount} | ${factor} |`
341377
);
342378
}
343379
}
@@ -363,6 +399,10 @@ function renderComparison(data, baselineData) {
363399
);
364400
}
365401

402+
// Split backends into local dev and production
403+
const localDevBackends = backends.filter((b) => b !== 'vercel');
404+
const productionBackends = backends.filter((b) => b === 'vercel');
405+
366406
// Separate benchmarks into regular and stream categories
367407
const regularBenchmarks = [];
368408
const streamBenchmarks = [];
@@ -375,39 +415,56 @@ function renderComparison(data, baselineData) {
375415
}
376416
}
377417

378-
// Render regular benchmarks first
379-
if (regularBenchmarks.length > 0) {
380-
for (const [benchName, benchData] of regularBenchmarks) {
381-
const baselineBenchData = baselineData?.[benchName] || null;
418+
// Helper to render both local dev and production tables for a benchmark
419+
const renderBenchmarkWithEnvironments = (benchName, benchData, isStream) => {
420+
const baselineBenchData = baselineData?.[benchName] || null;
421+
422+
console.log(`## ${benchName}\n`);
423+
424+
// Render Local Development table
425+
if (localDevBackends.length > 0) {
426+
console.log('#### 💻 Local Development\n');
382427
renderBenchmarkTable(
383428
benchName,
384429
benchData,
385430
baselineBenchData,
386431
apps,
387-
backends,
388-
false
432+
localDevBackends,
433+
isStream,
434+
{ showHeading: false }
389435
);
390436
}
437+
438+
// Render Production table
439+
if (productionBackends.length > 0) {
440+
console.log('#### ▲ Production (Vercel)\n');
441+
renderBenchmarkTable(
442+
benchName,
443+
benchData,
444+
baselineBenchData,
445+
apps,
446+
productionBackends,
447+
isStream,
448+
{ showHeading: false }
449+
);
450+
}
451+
};
452+
453+
// Render regular benchmarks
454+
for (const [benchName, benchData] of regularBenchmarks) {
455+
renderBenchmarkWithEnvironments(benchName, benchData, false);
391456
}
392457

393458
// Render stream benchmarks in a separate section
394459
if (streamBenchmarks.length > 0) {
395460
console.log('---\n');
396-
console.log('### Stream Benchmarks\n');
461+
console.log('## Stream Benchmarks\n');
397462
console.log(
398463
'_Stream benchmarks include Time to First Byte (TTFB) metrics._\n'
399464
);
400465

401466
for (const [benchName, benchData] of streamBenchmarks) {
402-
const baselineBenchData = baselineData?.[benchName] || null;
403-
renderBenchmarkTable(
404-
benchName,
405-
benchData,
406-
baselineBenchData,
407-
apps,
408-
backends,
409-
true
410-
);
467+
renderBenchmarkWithEnvironments(benchName, benchData, true);
411468
}
412469
}
413470

@@ -425,6 +482,12 @@ function renderComparison(data, baselineData) {
425482
let fastestApp = null;
426483
let fastestTime = Infinity;
427484

485+
// Skip community worlds in framework comparison (they only run against nextjs-turbopack)
486+
const isCommunityWorld = worldConfig[backend]?.community === true;
487+
if (isCommunityWorld) {
488+
continue;
489+
}
490+
428491
for (const app of apps) {
429492
const metrics = benchData[app]?.[backend];
430493
if (metrics) {
@@ -451,6 +514,12 @@ function renderComparison(data, baselineData) {
451514
let fastestTime = Infinity;
452515

453516
for (const backend of backends) {
517+
// Skip community worlds for non-nextjs-turbopack frameworks
518+
const isCommunityWorld = worldConfig[backend]?.community === true;
519+
if (isCommunityWorld && app !== 'nextjs-turbopack') {
520+
continue;
521+
}
522+
454523
const metrics = benchData[app]?.[backend];
455524
if (metrics) {
456525
const time = metrics.workflowTime ?? metrics.wallTime;
@@ -479,6 +548,13 @@ function renderComparison(data, baselineData) {
479548
console.log('|:------|:---------------------|-----:|');
480549

481550
for (const backend of backends) {
551+
// Skip community worlds in "Fastest Framework by World" summary
552+
// (they only run against nextjs-turbopack, so framework comparison doesn't apply)
553+
const isCommunityWorld = worldConfig[backend]?.community === true;
554+
if (isCommunityWorld) {
555+
continue;
556+
}
557+
482558
const worldInfo = worldConfig[backend] || { emoji: '', label: backend };
483559
const frameworkWins = frameworkWinsByWorld[backend] || {};
484560

@@ -554,14 +630,21 @@ function renderComparison(data, baselineData) {
554630
'- **Wall Time**: Total testbench time (trigger workflow + poll for result)'
555631
);
556632
console.log('- **Overhead**: Testbench overhead (Wall Time - Workflow Time)');
633+
console.log('- **Samples**: Number of benchmark iterations run');
557634
console.log(
558635
'- **vs Fastest**: How much slower compared to the fastest configuration for this benchmark'
559636
);
560637
console.log('');
561638
console.log('**Worlds:**');
562-
console.log('- 💻 Local: In-memory filesystem world');
563-
console.log('- 🐘 Postgres: PostgreSQL database world');
564-
console.log('- ▲ Vercel: Vercel production world');
639+
console.log('- 💻 Local: In-memory filesystem world (local development)');
640+
console.log('- 🐘 Postgres: PostgreSQL database world (local development)');
641+
console.log('- ▲ Vercel: Vercel production/preview deployment');
642+
// Add community worlds to legend
643+
for (const [id, config] of Object.entries(worldConfig)) {
644+
if (config.community) {
645+
console.log(`- 🌐 ${config.label}: Community world (local development)`);
646+
}
647+
}
565648
console.log('</details>');
566649
}
567650

0 commit comments

Comments
 (0)