Lands the OpenovaFlow Foundation end-to-end so the catalyst-ui FlowPage
consumes the new openova-flow-server's merged multi-region SSE stream
(`GET /api/v1/flows/{deploymentId}/stream`) and renders the per-region
adapter-flux emissions directly via @openova/flow-canvas. Closes the
revert from PR #1394 and unblocks the prov #34 multi-region 2-bubble
demo (fsn1 + hel1 each install bp-gateway-api → two bubbles).
# What ships
## A. npm workspaces at repo root
• New `package.json` declares `openova-monorepo` private root with
three workspaces: products/openova-flow/{core,canvas} +
products/catalyst/bootstrap/ui.
• Root `package-lock.json` resolves @openova/flow-* as workspace
symlinks into the hoisted node_modules tree.
• react / react-dom / d3-* are now hoisted into the monorepo's root
node_modules, so flow-canvas's bare `import 'react'` resolves via
standard upward-walking node_modules — no per-package sibling
node_modules required (the root cause of PR #1389's build break).
## B. Catalyst-ui consumes @openova/flow-* via file: deps
• catalyst-ui's `package.json` adds `@openova/flow-core` and
`@openova/flow-canvas` as `file:../../../openova-flow/{core,canvas}`
deps so `npm ci` from within catalyst-ui (today's CI path) keeps
working without needing root-level `npm ci -ws`.
• Vite `resolve.alias` + tsconfig `paths` bind `@openova/flow-core`
and `@openova/flow-canvas` to the source-only `./src/index.ts`
entry points. `dedupe: ['react', 'react-dom']` guards against
double-instancing.
• `tsconfig.app.json` `include` adds the two flow-package src trees
so tsc covers them with catalyst-ui's strict settings (instead of
each package's standalone `tsc -p tsconfig.json`, which lacks the
React/d3 node_modules siblings).
## C. New SSE consumer + bridge
• `src/lib/openflow-adapter-sse.ts` — `useFlowStream` React hook +
pure `reduceFlowMessage` reducer. Consumes the contract verbatim
(snapshot / upsert-flow / upsert-nodes / upsert-rels / delete-nodes
/ delete-rels). Owns the EventSource lifecycle, GET /snapshot
pre-paint, capped exponential reconnect.
• `src/lib/flow-bridge.ts` — catalyst-specific glue:
`CATALYST_STATUS_PALETTE` (mirrors `--bubble-*` CSS tokens onto
`StatusTone`), `flowStateToArrays` (Map→Array materialiser),
`regionDescriptorsFromFlow` (derives FlowCanvas regions from live
region tags + optional wizard-store augmentation), and
`rollupFlowStatus` (provisioning-status rollup on the new
contract).
• NOT a Job-shape bridge — the legacy Job adapter from PR #1389
is gone. catalyst-ui never goes through Catalyst's legacy Job model
again; the SSE stream IS the source of truth.
## D. FlowPage.tsx rewired
• Drives `FlowCanvas` from `@openova/flow-canvas` directly off the
new hook.
• Multi-region support comes for free: per-region adapter-flux tags
every emitted FlowNode with `region: '<location-code>'`; the
canvas's swimlane layout buckets by `region`. Single-region
provisions render identically to before via a synthetic
fallback descriptor.
• Embedded mode preserved for JobDetail.
## E. Containerfile preserves CI build
• COPY products/openova-flow/{core,canvas}/{package.json,src/}
BEFORE `npm ci` so `file:` deps validate. Subsequent
`COPY products/` layers the rest (CONTRACT.md etc.) in.
# Tests
• 23 new tests, 0 regressions on adjacent areas:
- `openflow-adapter-sse.test.ts` (6) — reducer covers all 6
FlowMessage variants including delete-nodes' rel-prune cascade
AND a multi-region merge case (fsn1 + hel1 both install
bp-gateway-api).
- `flow-bridge.test.ts` (10) — palette completeness, Map→Array
ordering, region descriptor derivation/fallback, status rollup
including group-exclusion and terminal-failure detection.
- `FlowPage.test.tsx` (7) — empty-state mount, StatusStrip, no
legacy mode toggle, embedded variant.
• flow-core: 20/20 passing; flow-canvas: 9/9 passing.
• Vitest full suite: 1130 pass / 87 fail (87 fails are pre-existing
on main and unrelated — PinInput6, ProvisionPage, etc.). Baseline
on main is 1052 pass / 88 fail / 27 failed files; this PR brings
78 new passing tests and lowers failing files from 27 → 18.
# Constraints honoured (Rule 7)
• NO `vite build` / `next build` / `npm run build` / `npx playwright
test` / `npx playwright install`. Only `tsc --noEmit` + `vitest
run` + `npm install --package-lock-only`.
• NO `kubectl apply` / chart manifests touched (Rule 11).
• NO hardcoded URLs / regions / k3s flags. Endpoint composed from
`API_BASE`; regions derived from live FlowNode tags; deploymentId
from `useParams` (Rule 18).
• Two-repo discipline: openova-io/openova only (Rule 21).
• Conventional commit + Claude co-author footer (Rule 20).
• isolation:"worktree" — work landed in a dedicated worktree.
# Canonical-seam citations (ARCHITECT-FIRST)
1. PR #1389's `flow-bridge.ts` — reference for the shape of a
catalyst-ui→@openova/flow contract layer. NOT conflated: that
bridge translated legacy Catalyst Jobs into FlowNodes; this one
consumes the new SSE FlowMessage stream directly with no Job
intermediary.
2. `useDeploymentEvents.ts` (line 526+, `openStream` + `onerror`
reconnect + capped retry) — canonical SSE consumer pattern in
this codebase. `useFlowStream` mirrors it (capped exponential
backoff, idempotent reducer over replayed buffered events).
# Definition of Done — post-merge verification plan
1. CI green (catalyst-build builds the new Containerfile path).
2. `curl -k -b /tmp/cz-cookie-prov27.txt
'https://console.openova.io/sovereign/api/v1/flows/5a175e0a88c99cec/snapshot' | jq`
→ nodes[] contains BOTH `fsn1/bp-gateway-api` AND `hel1/bp-gateway-api`.
3. Browser test: navigate to
`https://console.openova.io/sovereign/provision/5a175e0a88c99cec/jobs/install-gateway-api`
→ expect TWO bubbles (one per region).
4. If snapshot is empty, inspect emitter DaemonSets:
`kubectl --context=omantel get pods -n openova-flow`.
Co-authored-by: hatiyildiz <269457768+hatiyildiz@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
113 lines
4.5 KiB
TypeScript
113 lines
4.5 KiB
TypeScript
/// <reference types="vitest" />
|
|
import type { Plugin } from 'vite'
|
|
import { defineConfig } from 'vitest/config'
|
|
import react from '@vitejs/plugin-react'
|
|
import tailwindcss from '@tailwindcss/vite'
|
|
import { resolve } from 'path'
|
|
import { spawnSync } from 'node:child_process'
|
|
|
|
const REPO_ROOT = resolve(__dirname, '../../../..')
|
|
|
|
/**
|
|
* Vite plugin that re-runs scripts/build-catalog.mjs whenever any
|
|
* platform/<name>/blueprint.yaml or products/<name>/blueprint.yaml changes
|
|
* during dev. Keeps the wizard's StepComponents grid live-reloading without
|
|
* requiring a manual `npm run build:catalog` after every Blueprint edit.
|
|
*
|
|
* Build mode runs the same script via `prebuild` in package.json, so this
|
|
* plugin only matters in dev.
|
|
*/
|
|
function rebuildCatalogOnYamlChange(): Plugin {
|
|
const catalogScript = resolve(__dirname, 'scripts/build-catalog.mjs')
|
|
|
|
function runCatalog(reason: string) {
|
|
const r = spawnSync(process.execPath, [catalogScript], { stdio: 'inherit' })
|
|
if (r.status !== 0) {
|
|
// eslint-disable-next-line no-console
|
|
console.error(`[build-catalog] failed (${reason})`)
|
|
}
|
|
}
|
|
|
|
return {
|
|
name: 'catalyst-rebuild-catalog',
|
|
apply: 'serve',
|
|
configureServer(server) {
|
|
// Watch the entire monorepo's platform/ + products/ for blueprint.yaml
|
|
// changes. Vite's chokidar instance handles dedup + glob.
|
|
server.watcher.add([
|
|
resolve(REPO_ROOT, 'platform/**/blueprint.yaml'),
|
|
resolve(REPO_ROOT, 'products/**/blueprint.yaml'),
|
|
])
|
|
const isBlueprintFile = (path: string) => /(?:^|\/)blueprint\.yaml$/.test(path)
|
|
const handle = (event: string) => (path: string) => {
|
|
if (!isBlueprintFile(path)) return
|
|
runCatalog(`${event} ${path}`)
|
|
server.ws.send({ type: 'full-reload', path: '*' })
|
|
}
|
|
server.watcher.on('add', handle('add'))
|
|
server.watcher.on('change', handle('change'))
|
|
server.watcher.on('unlink', handle('unlink'))
|
|
},
|
|
}
|
|
}
|
|
|
|
export default defineConfig({
|
|
// base: '/' — path-agnostic for all deployment contexts.
|
|
//
|
|
// On Sovereigns (console.<sov>.omani.works/) the HTTPRoute passes
|
|
// through to nginx root; assets must be at /assets/*, not
|
|
// /sovereign/assets/*. On contabo (console.openova.io/sovereign/*),
|
|
// the Traefik strip-sovereign Middleware strips the /sovereign prefix
|
|
// BEFORE forwarding to nginx, so nginx still sees /assets/* — both
|
|
// cases resolve correctly with base: '/'.
|
|
//
|
|
// Issue #596: the previous base: '/sovereign/' caused blank pages on
|
|
// every Sovereign cluster because the browser requested
|
|
// /sovereign/assets/index-*.js but nginx (serving dist at /) returned 404.
|
|
base: '/',
|
|
plugins: [tailwindcss(), react(), rebuildCatalogOnYamlChange()],
|
|
resolve: {
|
|
alias: {
|
|
// OpenovaFlow Foundation — the @openova/* packages live in
|
|
// products/openova-flow/{core,canvas}/. They publish source-only
|
|
// entry points (./src/index.ts) so Vite + Vitest + tsc all bind
|
|
// straight to TS source. Workspaces (root package.json) hoist
|
|
// react/react-dom/d3-* into the monorepo's node_modules tree so
|
|
// the canvas source's bare imports resolve via standard
|
|
// upward-walking node_modules — no per-package node_modules
|
|
// sibling needed. Per-package aliases are listed BEFORE the '@'
|
|
// alias because @rollup/plugin-alias matches whole-name (so
|
|
// ordering is academic) but the documented convention is
|
|
// "longer key first" defensively.
|
|
'@openova/flow-core': resolve(__dirname, '../../../openova-flow/core/src/index.ts'),
|
|
'@openova/flow-canvas': resolve(__dirname, '../../../openova-flow/canvas/src/index.ts'),
|
|
'@': resolve(__dirname, './src'),
|
|
},
|
|
// Workspaces install one copy of react/react-dom at the monorepo
|
|
// root; dedupe makes sure both the catalyst-ui bundle AND the
|
|
// flow-canvas source bind to the same instance (otherwise React
|
|
// throws "invalid hook call" when two copies coexist).
|
|
dedupe: ['react', 'react-dom'],
|
|
},
|
|
server: {
|
|
port: 5173,
|
|
proxy: {
|
|
// With base: '/', API_BASE = '/api' in dev too. Direct proxy to
|
|
// catalyst-api on localhost:8080 — no prefix rewrite needed.
|
|
'/api': {
|
|
target: 'http://localhost:8080',
|
|
changeOrigin: true,
|
|
},
|
|
},
|
|
},
|
|
// Vitest config — drives `npm run test` in this package. The test runner
|
|
// shares Vite's `resolve.alias` so `@/...` imports work in tests too.
|
|
test: {
|
|
environment: 'jsdom',
|
|
globals: false,
|
|
css: false,
|
|
include: ['src/**/*.test.{ts,tsx}'],
|
|
setupFiles: ['src/test/setup.ts'],
|
|
},
|
|
})
|