feat: rewrite shinycannon in TypeScript#1
Merged
Conversation
Add a beautiful interactive terminal experience using yoctocolors and ora: - Startup banner showing target URL, workers, duration, and output dir - Warmup spinner with worker readiness counter - Live loaded-phase display with progress bar, countdown timer, and running/done/failed stats (updates every second) - Shutdown spinner and final summary - Colored CLI help text: per-argument colors (green for recording, magenta for app-url), cyan option flags, bold section headings, yellow environment variable names - TTY-only: falls back to existing logger-based output when piped
- Fix replaceTokens to only replace known allowed tokens instead of
scanning for all ${UPPERCASE} patterns, preventing false positives
from JS template literals in minified widget code (e.g. plotly ESM)
- Recognize Python Shiny's empty message format in canIgnore
({"values":{}} with objects, not just {"values":[]} with arrays)
- Add comm_id mapping for Jupyter widget playback: capture recorded
vs actual comm_id from shinywidgets_comm_open messages during
WS_RECV, substitute in outgoing WS_SEND messages
Ported from PR rstudio#74 (Kotlin) to the TypeScript rewrite.
Split single sequential CI job into parallel jobs for lint, typecheck, test (Node 20 + 22), and build. Add concurrency controls, least-privilege permissions, and a gate job for branch protection.
The rstudio#74 port's isEmpty() helper treated both empty arrays and empty objects as "empty", which caused R Shiny's legitimate response {"errors":{},"values":{},"inputMessages":[]} to be filtered out. This starved WS_RECV events, causing 30-second timeouts. Reverts to the Kotlin original's behavior: only ignore when all three fields (errors, values, inputMessages) are empty arrays.
The TypeScript rewrite has passed the Kotlin parity audit and all 171 tests. The original Kotlin source is preserved in git history on the main branch.
Restructure the CLI to use Commander.js subcommands, moving all existing arguments and options under `shinycannon loadtest`. This makes room for future subcommands like `record` and `report`. - `shinycannon` with no args shows root help listing subcommands - `shinycannon loadtest` with no args shows loadtest-specific help - `shinycannon loadtest <recording> [app-url] [options]` runs the test
Thread AbortSignal from worker orchestration through session playback to enable near-instant shutdown. Cancellable sleep, fetch, and WebSocket receive operations replace the previous design where workers could only observe shutdown between complete session replays.
Use response.url (the final URL after redirects) when persisting Set-Cookie headers to the cookie jar, so cookies are associated with the correct domain when redirects cross origins.
- Rename package from `shinycannon` to `@posit-dev/shinyloadtest` (v2.0.0-alpha.1) - Rename `loadtest` subcommand to `replay` - Rename env vars to `SHINYLOADTEST_*` with `SHINYCANNON_*` fallback - Add `shinycannon` as legacy bin alias in main package - Add `packages/shinycannon/` stub package for `npx shinycannon` support - Rename output version file to `shinyloadtest-version.txt` - Update all tests, docs, and branding references
Show total events completed and events/second rate during the loaded phase. When running with multiple workers, also display per-worker averages on a separate line.
- Wrap canIgnore() in try/catch in the WebSocket message handler so malformed SockJS frames route through triggerFailure() instead of crashing the process with an uncaught exception - Add HTTP 401 to isProtected() check alongside 403/404; note that 302 redirects are auto-followed by fetch so they never reach this check - Add regression tests for both paths
ab7a2c4 to
b9f2c32
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Complete rewrite of shinycannon from Kotlin/JVM to TypeScript/Node.js,
republished as
@posit-dev/shinyloadtest. This is a 1:1 behavioralrewrite — same recording format, same CSV output format (compatible with
the shinyloadtest R package).
CLI change: The primary CLI is now
shinyloadtest replay(with ashinycannonalias for backwards compatibility).Key changes:
npm install @posit-dev/shinyloadtest/npx @posit-dev/shinyloadtestws,tough-cookie,commander)tsup(esbuild) producing ESM bundleArchitecture: 13 focused modules (types, recording, tokens, sockjs,
url, http, websocket, detect, auth, logger, output, session, worker, cli,
main, ui) with discriminated unions for event types, per-session cookie
jars, and async queue-based WebSocket message buffering.
Behavioral parity: All 12 behavioral constraints from the original
Kotlin implementation are preserved and tested (recording validation,
status code handling, WS queue bounds, key matching, output format, etc.).
Verification
All CI checks (lint, typecheck, build, test) pass.
Originally opened as rstudio#76.