Files
Stirling-PDF/frontend/editor/vitest.config.ts
T
ConnorYoh 8df49ac053 feat(portal): build portal for SaaS and self-hosted via file-override layer (#6900)
## What

Ground-work so the admin portal can build for the **SaaS flavor**
alongside self-hosted, using the editor's existing **build-time
file-override** mechanism — no runtime flavor flags. This PR
demonstrates SaaS end-to-end (single-login + Usage page loading via the
inherited Supabase session) without changing self-hosted behaviour.

This is intentionally scoped as foundations, not the whole feature.

## How

**Build hook**
- `tsconfig.saas.vite.json`: `@portal/*` now cascades
(`src/saas/portal/*` → `src/portal/*`); added `@portalCore/*` for the
explicit base path.
- New `src/portal-saas/` layer (sibling of `src/portal`) holds SaaS-only
overrides, so `@app/*` resolves only editor layers and `@portal/*` only
portal layers. Self-hosted builds never import it (tree-shaken).

**Seams live in the api-client + composition layers — never in page
components**
- `saasApiBase` — base URL source (self-hosted: `VITE_SAAS_API_URL`;
SaaS reuses the single `VITE_API_BASE_URL` backend).
- `portalSaasSession` — flavor-agnostic token from the shared Supabase
client.
- `PortalAuthBoundary` — self-hosted: Spring `AuthProvider` +
`AuthGate`; SaaS: Supabase `AuthProvider` + session-only gate (inherits
the SaaS session, so no second login).

**Link concept pulled out of the Usage page (one clean cut)**
- `Usage` is now a link-free wallet renderer with generic
`onWalletLoaded` / `onReauth` callbacks; it always loads the wallet and
has zero flavor awareness.
- `PortalBillingGate` is the single flavor seam: self-hosted gates on
link (prompt when unlinked; wires the callbacks onto link/tier +
re-auth), SaaS is a passthrough that renders `Usage` directly.
- Keeps the flavor switch out of the page entirely (no per-flavor code
in `Usage`).

## Testing
Green locally and in CI (CI runs the umbrella `task
frontend:check:all`):
- `task frontend:typecheck:all` — clean across all 7 build variants
- `task frontend:test` — vitest suites pass (portal + saas cover this
change; 146 tests)
- `task frontend:build:saas` and `task frontend:build:proprietary` —
both green
- `task frontend:lint` and `task frontend:format:check` — clean

## Also in this PR (added after the initial foundations)
- **Tier from wallet + full link-layer excision on SaaS.** `TierContext`
no longer reads `LinkContext` (via a `usePlanTier` seam: self-hosted
from link state, SaaS from `wallet.status`), and the SaaS
`PortalProviders` drops `LinkProvider` / `AccountLinkProvider` /
`LinkModalHost` entirely — the link machinery is *absent* from the SaaS
bundle, not mounted-but-inert.

## Deliberately out of scope (follow-ups)
- SaaS-only read-only "connected servers" settings view.
- Shared wallet source so the SaaS tier badge and the Usage page don't
both fetch `/payg/wallet` (harmless double-fetch today).
2026-07-08 12:07:54 +00:00

152 lines
3.9 KiB
TypeScript

import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react-swc";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
test: {
globals: true,
environment: "jsdom",
setupFiles: ["./src/core/setupTests.ts"],
css: false,
exclude: [
"node_modules/",
"src/**/*.spec.ts", // Exclude Playwright E2E tests
"src/tests/test-fixtures/**",
],
testTimeout: 10000,
hookTimeout: 10000,
coverage: {
reporter: ["text", "json", "html"],
exclude: [
"node_modules/",
"src/core/setupTests.ts",
"src/proprietary/setupTests.ts",
"src/saas/setupTests.ts",
"**/*.d.ts",
"src/tests/test-fixtures/**",
"src/**/*.spec.ts",
],
},
projects: [
{
test: {
name: "core",
include: ["src/core/**/*.test.{ts,tsx}"],
environment: "jsdom",
globals: true,
setupFiles: ["./src/core/setupTests.ts"],
},
plugins: [
react(),
tsconfigPaths({
projects: ["./tsconfig.core.vite.json"],
}),
],
esbuild: {
target: "es2020",
},
},
{
test: {
name: "portal",
include: ["src/portal/**/*.test.{ts,tsx}"],
environment: "jsdom",
globals: true,
setupFiles: ["./src/portal/setupTests.ts"],
},
plugins: [
react(),
tsconfigPaths({
// Broad project so @app/@portal resolve in every editor file the
// portal tests pull in (core/ui, core, ...).
projects: ["./tsconfig.portal.vite.json"],
}),
],
esbuild: {
target: "es2020",
},
},
{
test: {
name: "proprietary",
include: ["src/proprietary/**/*.test.{ts,tsx}"],
environment: "jsdom",
globals: true,
setupFiles: ["./src/core/setupTests.ts"],
},
plugins: [
react(),
tsconfigPaths({
projects: ["./tsconfig.proprietary.vite.json"],
}),
],
esbuild: {
target: "es2020",
},
},
{
test: {
name: "desktop",
include: ["src/desktop/**/*.test.{ts,tsx}"],
environment: "jsdom",
globals: true,
setupFiles: ["./src/core/setupTests.ts"],
},
plugins: [
react(),
tsconfigPaths({
projects: ["./tsconfig.desktop.vite.json"],
}),
],
esbuild: {
target: "es2020",
},
},
{
test: {
name: "saas",
// src/saas = editor-saas layer; src/portal-saas = the portal's saas
// overrides (sibling to src/portal). Both build under the saas flavor,
// so both resolve @portal via the saas cascade (tsconfig.saas.vite.json).
include: [
"src/saas/**/*.test.{ts,tsx}",
"src/portal-saas/**/*.test.{ts,tsx}",
],
environment: "jsdom",
globals: true,
setupFiles: ["./src/saas/setupTests.ts"],
},
plugins: [
react(),
tsconfigPaths({
projects: ["./tsconfig.saas.vite.json"],
}),
],
esbuild: {
target: "es2020",
},
},
{
test: {
name: "prototypes",
include: ["src/prototypes/**/*.test.{ts,tsx}"],
environment: "jsdom",
globals: true,
setupFiles: ["./src/core/setupTests.ts"],
},
plugins: [
react(),
tsconfigPaths({
projects: ["./tsconfig.prototypes.vite.json"],
}),
],
esbuild: {
target: "es2020",
},
},
],
},
esbuild: {
target: "es2020",
},
});