@lessly/app-dev
v0.1.1
Published
Vite plugin for local development of Lessly Apps
Readme
@lessly/app-dev
Vite plugin for local development of Lessly Apps. It proxies /lessly-api/* to the
Lessly REST API with an automatically-injected bearer token, exposes the dev
import.meta.env values your App needs, and serves the Module-Federation manifest
at the origin root.
Usage
// vite.config.ts
import { defineConfig } from 'vite';
import { lesslyAppDev } from '@lessly/app-dev';
export default defineConfig({
plugins: [lesslyAppDev()],
});Environment
Set these in .env.local (they are read from your project's env files):
| Variable | Required | Purpose |
| ------------------- | -------- | -------------------------------------------------------------- |
| VITE_PRODUCT_ID | standalone only | The Lessly product your App runs under. Required for standalone dev; optional when composed under the shell (dev:federation/dev:shell), which supplies it at runtime. The plugin warns and continues if unset — it never fails the build. |
| VITE_API_URL | no | Override the REST API base (defaults per LESSLY_ENV). |
| VITE_PRODUCT_SLUG | no | Your product slug (the first URL segment, e.g. roslov). Used only to build the ready shell URL in the dev-remote banner. It is not the dev-remote key — see below. |
| LESSLY_ENV | no | staging (default) or prod. |
| DEV_API_KEY | no | Headless auth (CI): exchanged for a short-lived token. Never exposed to the client bundle. |
Loading your local App into the Workspace shell
On dev:shell the plugin prints a paste-ready banner: where the App is running,
a ready ?dev-remote= shell URL, and the localStorage fallback:
localStorage.setItem('lessly:dev-remote', '<app-slug>=http://localhost:5174')The contract has two halves that are easy to confuse:
- key — the App's Module Federation remote name, i.e.
idinlessly.app.yaml(=APP_NAMEinconfig/app-name.mjs). Not the product slug. - value — the App's own dev origin, a bare origin with no base path. The
shell fetches
<origin>/mf-manifest.jsonat the origin root; the plugin's manifest middleware rewrites that to the based path where Module Federation answers it.
The origin in the banner is read from the actually-listening server, so it stays correct if the App ends up on a different port.
Chrome or Firefox only — Safari has no localhost carve-out and blocks an https
shell from loading an http://localhost remote.
Authentication
The plugin injects Authorization: Bearer <token> on every /lessly-api/* request
using, in order: a valid cached token → a stored token in ~/.lessly/credentials.json
→ a refreshed token → DEV_API_KEY exchange → interactive device login. Token
refresh is coordinated across processes with a file lock beside the credentials file.
Scope: REST-only (v1)
This plugin proxies HTTP REST calls only. WebSocket upgrades on /lessly-api/*
are intentionally not proxied in v1 — Lessly Apps use the REST API for local
development, and adding WS proxying (and its auth/reconnect semantics) is deferred
until a concrete need exists. If you require realtime transport locally, connect to
the target service directly rather than through the dev proxy.
Cross-site requests to the dev proxy (Sec-Fetch-Site: cross-site) are rejected as a
lightweight drive-by-CSRF defense.
