creevey
Version:
Cross-browser screenshot testing tool for Storybook with fancy UI Runner
234 lines (150 loc) • 7.95 kB
Markdown
# Design: Dev-Only Client Statics Bootstrap
**Date:** 2026-05-13
**Status:** Approved
**Goal:** Keep on-demand client UI builds for source-checkout local development while removing that fallback from production server/runtime code.
## Problem
`ensureClientStatics()` currently lives in `src/server/utils.ts` and is called from runtime server flows:
- `src/server/master/start.ts`
- `src/server/report.ts`
- `copyStatics()` in `src/server/utils.ts`
That makes production-oriented server code responsible for building the Vite UI bundle on demand when `dist/client/web/index.html` is missing.
This is unnecessary for the published package because the package is built before publish. The fallback exists to support repo-local execution via `tsx ./src/cli`, where the server still expects the built client assets in `dist/client/web`.
The current placement mixes two responsibilities:
- production/runtime asset consumption
- local development bootstrap behavior
## Selected Approach
Move the on-demand build fallback to a dev-only bootstrap path at the CLI layer.
Production/runtime code will only consume already-built client statics. Repo-local execution will preserve the existing convenience by building the UI bundle before entering server flows when needed.
## Why This Boundary Fits
There are two distinct execution modes in this repo:
1. **Repo-local development**
- `package.json` script `creevey` runs `tsx ./src/cli`
- source files are executed directly
- `dist/client/web` may not exist yet
2. **Published package usage**
- `package.json.bin` points to `dist/cli.js`
- package is built before publish
- client statics should already be present in the packed artifact
The CLI boundary is the cleanest place to keep repo-local bootstrap logic without keeping that behavior in operational server modules.
## Architecture
### Before
```text
CLI
-> server/report or server/start
-> ensureClientStatics()
-> maybe vite build
-> serve/copy dist/client/web
```
### After
```text
Repo-local CLI
-> dev-only ensure client statics
-> maybe vite build
-> server/report or server/start
-> require dist/client/web
Published CLI
-> server/report or server/start
-> require dist/client/web
```
## Code Changes
### 1. Remove build-on-demand logic from server utilities
`src/server/utils.ts` will no longer export `ensureClientStatics()`.
It will retain `getClientDir()` and introduce a strict accessor for runtime usage, for example:
- `getRequiredClientDir()`
Behavior:
- resolve `dist/client/web`
- verify `index.html` exists
- return the directory if present
- otherwise throw a clear error instructing the operator to run `yarn build` or `yarn build:client`
This keeps runtime assumptions explicit.
### 2. Add a dev-only bootstrap helper
Create a new module outside the server runtime path, for example:
- `src/dev/ensure-client-statics.ts`
Behavior:
- resolve `dist/client/web/index.html`
- return immediately when present
- otherwise run Vite build using the existing project `vite.config.mts`
- verify the build output exists
- throw a clear error if the build fails
This module owns the current fallback logic and is only used by repo-local source execution.
### 3. Move the fallback call to the CLI layer
Update `src/creevey.ts` so that before invoking `creevey(command, options)`, it conditionally runs the dev-only helper when:
- command is `report`, or
- command is `test` and `options.ui === true`
This preserves the current user-facing local-dev behavior:
- `yarn creevey report`
- `yarn creevey test --ui`
Both will still build the client UI on demand from a source checkout.
### 4. Update server call sites to use strict asset access
Replace `ensureClientStatics()` usages in:
- `src/server/report.ts`
- `src/server/master/start.ts`
- `copyStatics()` in `src/server/utils.ts`
with the strict runtime accessor or equivalent non-building path.
Server code will no longer import Vite or attempt builds.
## Files Changed
| File | Action | Reason |
| ---------------------------------- | ------ | ---------------------------------------------------------- |
| `src/server/utils.ts` | Update | Remove runtime fallback build, add strict statics accessor |
| `src/server/report.ts` | Update | Stop invoking build-on-demand from runtime flow |
| `src/server/master/start.ts` | Update | Stop invoking build-on-demand from runtime flow |
| `src/creevey.ts` | Update | Add repo-local bootstrap before entering server flows |
| `src/dev/ensure-client-statics.ts` | Create | Hold local-dev-only Vite build fallback |
## Behavior Changes
### Local Development
- Running `yarn creevey report` in a source checkout without `dist/client/web` will build the UI bundle automatically, then continue.
- Running `yarn creevey test --ui` in a source checkout without `dist/client/web` will do the same.
- Non-UI flows will not trigger client builds.
### Published / Production Runtime
- Runtime code will never build the UI bundle on demand.
- Missing `dist/client/web/index.html` becomes a packaging or installation error.
- The error message should explain that the package is missing required client assets and should suggest `yarn build` or `yarn build:client` for source checkouts.
## Error Handling
### Dev Bootstrap Failure
If the on-demand build fails:
- surface the original Vite failure reason
- prefix it with a Creevey-specific message such as `Failed to build Creevey web UI`
### Runtime Missing Assets
If runtime code cannot find `dist/client/web/index.html`:
- fail immediately
- use a deterministic message
- make it obvious that runtime is not expected to build assets
## Testing
### Manual Verification
1. Remove `dist/client/web`
2. Run `yarn creevey report`
3. Verify the UI bundle is built automatically and the server starts
4. Remove `dist/client/web` again
5. Run `yarn creevey test --ui`
6. Verify the UI bundle is built automatically and UI mode starts
7. Run a non-UI command and verify it does not build the client bundle
8. Run the built/package-style path and verify no runtime build logic is invoked
### Regression Checks
- Report generation still copies built assets into `reportDir`
- UI mode still serves the same `dist/client/web` output
- Server modules no longer depend on Vite build behavior
## Trade-offs
### Benefits
- Production/runtime server code becomes simpler and more explicit
- Local-dev convenience is preserved
- Build fallback responsibility moves to the correct boundary
- Packaging problems are exposed instead of silently repaired at runtime
### Costs
- The CLI layer gains a small amount of environment-specific bootstrap logic
- The codebase now has two related helpers: one strict runtime accessor and one dev bootstrap helper
This is acceptable because the responsibilities are now clearly separated.
## Alternatives Considered
### Keep the fallback in runtime guarded by environment
Rejected because production code would still contain the build-on-demand path, which does not meet the goal.
### Add a separate dev CLI command
Rejected because it changes the local workflow more than necessary. The selected approach preserves existing commands.
## Rollback Plan
If the separation causes issues:
1. Restore `ensureClientStatics()` in `src/server/utils.ts`
2. Reintroduce runtime calls in `report.ts`, `start.ts`, and `copyStatics()`
3. Remove the CLI bootstrap helper
## Success Criteria
- Local source-checkout UI flows still auto-build client statics when missing
- Published/runtime server code does not perform on-demand Vite builds
- Missing client statics in runtime fail clearly and immediately
- Existing UI and report behavior remains otherwise unchanged