perf(cli): experimental bundled distribution — 306MB/21.8s → 26MB/4.3s cold npx#1593
perf(cli): experimental bundled distribution — 306MB/21.8s → 26MB/4.3s cold npx#1593shapirodaniel wants to merge 1 commit into
Conversation
📦 Bundle Stats —
|
| Metric | Value | vs main (efb5705) |
|---|---|---|
| Internal (raw) | 4.7 KB | +2.5 KB, +112.8% |
| Internal (gzip) | 1.7 KB | +946 B, +112.9% |
| Bundled (raw) | 10.11 MB | -1.09 MB, -9.7% |
| Bundled (gzip) | 1.85 MB | -268.8 KB, -12.4% |
| Import time | 818ms | -57ms, -6.6% |
bin:sanity
| Metric | Value | vs main (efb5705) |
|---|---|---|
| Internal (raw) | 782 B | - |
| Internal (gzip) | 423 B | - |
| Bundled (raw) | 9.90 MB | - |
| Bundled (gzip) | 1.78 MB | - |
| Import time | 2.19s | +94ms, +4.5% |
🗺️ View treemap · Artifacts
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
📦 Bundle Stats — @sanity/cli-core
Compared against main (efb5705b)
| Metric | Value | vs main (efb5705) |
|---|---|---|
| Internal (raw) | 115.4 KB | +1.3 KB, +1.2% |
| Internal (gzip) | 29.6 KB | +452 B, +1.5% |
| Bundled (raw) | 21.76 MB | +1.1 KB, +0.0% |
| Bundled (gzip) | 3.46 MB | +344 B, +0.0% |
| Import time | 802ms | +15ms, +1.9% |
🗺️ View treemap · Artifacts
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
📦 Bundle Stats — @sanity/cli-build
Compared against main (efb5705b)
@sanity/cli-build/_internal/build
| Metric | Value | vs main (efb5705) |
|---|---|---|
| Internal (raw) | 113.2 KB | - |
| Internal (gzip) | 28.7 KB | - |
| Bundled (raw) | 17.75 MB | +1.1 KB, +0.0% |
| Bundled (gzip) | 3.56 MB | +411 B, +0.0% |
| Import time | 1.16s | +8ms, +0.7% |
@sanity/cli-build/_internal/env
| Metric | Value | vs main (efb5705) |
|---|---|---|
| Internal (raw) | 1.8 KB | - |
| Internal (gzip) | 644 B | - |
| Bundled (raw) | 1.31 MB | - |
| Bundled (gzip) | 333.8 KB | - |
| Import time | 128ms | +1ms, +0.7% |
@sanity/cli-build/_internal/extract
| Metric | Value | vs main (efb5705) |
|---|---|---|
| Internal (raw) | 8.6 KB | - |
| Internal (gzip) | 2.7 KB | - |
| Bundled (raw) | 156.1 KB | +1.1 KB, +0.7% |
| Bundled (gzip) | 39.9 KB | +429 B, +1.1% |
| Import time | 253ms | +2ms, +0.8% |
🗺️ ./_internal/env · ./_internal/extract · @sanity/cli-build:./_internal/build treemap too large to embed · Artifacts
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
📦 Bundle Stats — create-sanity
Compared against main (efb5705b)
| Metric | Value | vs main (efb5705) |
|---|---|---|
| Internal (raw) | 908 B | - |
| Internal (gzip) | 483 B | - |
| Bundled (raw) | 931 B | - |
| Bundled (gzip) | 491 B | - |
| Import time | ❌ ChildProcess denied: node | - |
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Coverage Delta
Comparing 6 changed files against main @ Overall Coverage
|
Live UAT report (personal org, fully automated)Ran a credentialed end-to-end UAT against a throwaway project in a personal org ( Green end-to-end (safe variant): login (SSO + Google provider flows) → Aggressive variant spot-checks against the same real project: One real bug found and fixed (f0713ed): Residual UAT gaps: interactive prompt flows (PTY), Linux, and CI e2e with the org's e2e credentials — the org-level SAML account cannot create projects ( |
Remaining validation gaps and suggested next stepsContext for what's left untested and some options for closing each item. Interactive promptsPrompt flows ( Risk seems low — the prompt library is included in the bundle and passes the module link-check — but nobody has confirmed a rendered prompt from the bundled build yet. Closing it: run LinuxAll validation so far ran on macOS/ARM. The bundle relies on npm selecting per-platform native binaries at install time, which has only been observed selecting the macOS ones — and the agent evals that motivated this work run on Linux. Closing it: install the tarball and run a few commands ( e2e suite with credentialsThe cli-e2e suite is the scripted equivalent of the manual UAT above, and needs the existing CI secrets ( One possible consolidationA single CI job on a Linux runner could cover most of the above: build the tarballs (both variants) → npm-install them → run Open questions
|
Linux e2e results (full suite, both variants)Follow-up on the "Linux" and "e2e suite with credentials" gaps from the previous comment: the full cli-e2e suite has now run on Linux against both bundled outputs, alongside an unbundled control, using credentials for a throwaway project in a personal org (same setup as the UAT above). Environment:
No new failures in either variant. The 8 failures are the same 8 tests in all three runs — all in The count differences are registry-mode bookkeeping, not lost coverage: registry mode skips 6 tests (3 A few things this closes beyond the headline:
Caveats and what's still open:
Rough repro, for anyone who wants to run it elsewhere: build the tarball ( |
Matrix extension: node 26 and linux-x64Follow-up to the Linux e2e comment above, closing the two cells it flagged as untested. Same setup (one container per run, control + both variants,
(Pass-count differences between control and variants are the registry-mode skip bookkeeping explained in the previous comment; the registry-skipped workbench/deploy tests were separately verified green against both variants.) What the numbers say:
Not covered: node 24 (bracketed by the 22 floor and 26 head — a CI job would cover it as a matrix row), and real x64 hardware (Rosetta is a translation layer; it exercised binary selection and full execution, but a native x64 CI runner is the definitive version). Both fold into the CI job sketched earlier, which is the remaining piece that makes any of this repeatable. |
f0713ed to
770485d
Compare
Description
this PR attempts to cut cold start times on
npx --yes @sanity/cliby inlining most dependencies, and loading the rest as they're neededBenchmarks
Initial bundle setup
Current sanity CLI
Experimental bundled version of CLI
Details
Most dependencies are compiled into the published package at build time rather than installed individually by npm. The larger development tooling (vite, tsx, jsdom; all used by commands like
devandbuild) either ships as regular dependencies ("safe" output) or is installed once on first use ("aggressive" output).The typegen/functions/blueprints command groups similarly install their implementation on first use, while their help text ships with the CLI. Commands that don't use this tooling (
login,init, dataset and document commands,--help) don't load or install it.Source changes
Open questions
experimentalbuild to get early signalFor reviewers
high level, there's a few key mechanisms worth auditing
scripts/bundle-experiment/README.md: design notes, full results, issues found during validation, and known gapspackages/@sanity/cli/src/util/loadEnv.ts(+ tests): replaces vite'sloadEnvwith a dotenv-based equivalentpackages/@sanity/cli-core: worker spawn and studio-worker config changesscripts/bundle-experiment/: the publish pipeline and validation harnessesTesting
--helpoutput matched, and executing each command to its natural stopping point (output, usage error, or auth requirement), in and outside a studio project, showed no differences in the runs performedschema extractoutput was byte-identical;buildproduced the same artifact list;devserved locallyNotes for release
N/A