@fluojs/studio 1.0.0-beta.1 → 1.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +10 -7
- package/README.md +10 -7
- package/dist/assets/index-D-3gdi9Y.js +128 -0
- package/dist/contracts.d.ts +35 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +60 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.html +1 -1
- package/package.json +2 -2
- package/dist/assets/index-CUoyTA_q.js +0 -128
package/README.ko.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<p><a href="./README.md"><kbd>English</kbd></a> <strong><kbd>한국어</kbd></strong></p>
|
|
4
4
|
|
|
5
|
-
fluo 런타임 내보내기의 공유 플랫폼 snapshot을 파일 기반으로
|
|
5
|
+
fluo 런타임 내보내기의 공유 플랫폼 snapshot을 파일 기반으로 확인하고 그래프로 렌더링하는 canonical provider입니다.
|
|
6
6
|
|
|
7
7
|
## 목차
|
|
8
8
|
|
|
@@ -23,14 +23,14 @@ pnpm add @fluojs/studio
|
|
|
23
23
|
|
|
24
24
|
배포된 패키지는 두 가지 caller-facing entrypoint를 제공합니다.
|
|
25
25
|
|
|
26
|
-
- `@fluojs/studio` / `@fluojs/studio/contracts`: snapshot 파싱, 필터링, Mermaid
|
|
26
|
+
- `@fluojs/studio` / `@fluojs/studio/contracts`: canonical snapshot 파싱, 필터링, Mermaid 그래프 렌더링 헬퍼
|
|
27
27
|
- `@fluojs/studio/viewer`: 패키징된 브라우저 뷰어 HTML 진입 파일
|
|
28
28
|
|
|
29
29
|
## 릴리스 정책
|
|
30
30
|
|
|
31
31
|
- `@fluojs/studio`는 fluo의 intended public publish surface에 포함되는 공개 배포 패키지입니다.
|
|
32
32
|
- Studio의 npm 설치 계약은 `pnpm add @fluojs/studio`이며, 저장소 내부 개발 경로는 계속 `pnpm --dir packages/studio dev`를 사용합니다.
|
|
33
|
-
- 이번 릴리스에서 지원하는 공개 패키지 표면은 파일 기반 뷰어와 문서화된 snapshot
|
|
33
|
+
- 이번 릴리스에서 지원하는 공개 패키지 표면은 파일 기반 뷰어와 문서화된 snapshot 소비, 필터링, 그래프 렌더링 계약까지입니다. 내부 workspace 연결 방식은 지원되는 설치 경로가 아닙니다.
|
|
34
34
|
|
|
35
35
|
## 사용 시점
|
|
36
36
|
|
|
@@ -41,7 +41,7 @@ pnpm add @fluojs/studio
|
|
|
41
41
|
|
|
42
42
|
## 빠른 시작
|
|
43
43
|
|
|
44
|
-
Studio는 fluo CLI에서 내보낸 JSON 파일을 소비합니다.
|
|
44
|
+
Studio는 fluo CLI에서 내보낸 JSON 파일을 소비합니다. 런타임은 snapshot을 생산하고, CLI는 검사 데이터를 내보내거나 위임하며, Studio는 뷰어와 자동화 호출자가 사용할 수 있도록 snapshot을 파싱, 필터링, 보기, 렌더링하는 공개 헬퍼를 소유합니다. 지원되는 inspect artifact에는 raw snapshot, standalone timing diagnostics, snapshot-plus-timing envelope, `fluo inspect --report`가 생성한 report artifact가 포함됩니다.
|
|
45
45
|
|
|
46
46
|
1. **Snapshot 내보내기**:
|
|
47
47
|
```bash
|
|
@@ -68,21 +68,24 @@ Studio는 fluo CLI에서 내보낸 JSON 파일을 소비합니다.
|
|
|
68
68
|
2. 시각화하려는 모듈이나 컴포넌트를 선택합니다.
|
|
69
69
|
3. **Export to Mermaid** 버튼을 사용하여 문서에 사용할 수 있는 텍스트 기반 다이어그램을 가져옵니다.
|
|
70
70
|
|
|
71
|
+
자동화에서는 `@fluojs/studio` 또는 `@fluojs/studio/contracts`에서 `renderMermaid(snapshot)`을 호출합니다. 이 헬퍼가 지원되는 snapshot-to-Mermaid 계약입니다. 런타임 패키지는 snapshot producer로 남고, Studio는 그래프 렌더링 시 내부 dependency edge와 외부 dependency node를 처리합니다.
|
|
72
|
+
|
|
71
73
|
## 공개 API
|
|
72
74
|
|
|
73
|
-
Studio는 주로 웹 애플리케이션이지만, 배포된 패키지는 도구/자동화가 사용할 수 있는 snapshot 소비 헬퍼도 함께 공개합니다.
|
|
75
|
+
Studio는 주로 웹 애플리케이션이지만, 배포된 패키지는 도구/자동화가 사용할 수 있는 snapshot 소비 헬퍼도 함께 공개합니다. `@fluojs/studio`를 snapshot 파싱, 필터링, Mermaid 그래프 렌더링 의미론의 canonical owner로 취급합니다.
|
|
74
76
|
|
|
75
77
|
| 규격 | 설명 |
|
|
76
78
|
|---|---|
|
|
77
79
|
| `PlatformShellSnapshot` | 애플리케이션 상태를 나타내는 핵심 데이터 구조입니다. |
|
|
78
80
|
| `PlatformDiagnosticIssue` | 플랫폼 오류 보고 및 수정을 위한 스키마입니다. |
|
|
79
81
|
| `parseStudioPayload(rawJson)` | CLI/export JSON을 Studio snapshot/timing envelope로 검증합니다. |
|
|
82
|
+
| `StudioReportArtifact` | CI/support 자동화를 위해 summary, snapshot, timing 데이터를 함께 보존한 `fluo inspect --report` artifact입니다. |
|
|
80
83
|
| `applyFilters(snapshot, filter)` | 원본 snapshot을 변경하지 않고 readiness/severity/query 필터를 적용합니다. |
|
|
81
|
-
| `renderMermaid(snapshot)` | 로드된 플랫폼 그래프를 Mermaid 텍스트로 변환합니다. |
|
|
84
|
+
| `renderMermaid(snapshot)` | 내부 컴포넌트 dependency edge와 외부 dependency node를 포함해 로드된 플랫폼 그래프를 Mermaid 텍스트로 변환합니다. |
|
|
82
85
|
|
|
83
86
|
### 배포 패키지 entrypoint
|
|
84
87
|
|
|
85
|
-
- `@fluojs/studio`: snapshot
|
|
88
|
+
- `@fluojs/studio`: snapshot 파싱/필터링/렌더링 자동화용 루트 헬퍼 배럴
|
|
86
89
|
- `@fluojs/studio/contracts`: 계약 헬퍼를 직접 가져오고 싶은 도구용 명시적 서브패스
|
|
87
90
|
- `@fluojs/studio/viewer`: 브라우저 뷰어 번들의 `dist/index.html` 진입 파일
|
|
88
91
|
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<p><strong><kbd>English</kbd></strong> <a href="./README.ko.md"><kbd>한국어</kbd></a></p>
|
|
4
4
|
|
|
5
|
-
File-first shared platform snapshot viewer for fluo runtime exports.
|
|
5
|
+
File-first shared platform snapshot viewer and canonical graph rendering provider for fluo runtime exports.
|
|
6
6
|
|
|
7
7
|
## Table of Contents
|
|
8
8
|
|
|
@@ -23,14 +23,14 @@ pnpm add @fluojs/studio
|
|
|
23
23
|
|
|
24
24
|
The published package serves two caller-facing entrypoints:
|
|
25
25
|
|
|
26
|
-
- `@fluojs/studio` / `@fluojs/studio/contracts` for snapshot parsing, filtering, and Mermaid
|
|
26
|
+
- `@fluojs/studio` / `@fluojs/studio/contracts` for the canonical snapshot parsing, filtering, and Mermaid graph rendering helpers.
|
|
27
27
|
- `@fluojs/studio/viewer` for the packaged browser viewer HTML entry file.
|
|
28
28
|
|
|
29
29
|
## Release Policy
|
|
30
30
|
|
|
31
31
|
- `@fluojs/studio` is part of the intended public publish surface for fluo.
|
|
32
32
|
- The npm install contract for Studio is `pnpm add @fluojs/studio`; local repo development still uses `pnpm --dir packages/studio dev`.
|
|
33
|
-
- Studio's public package surface in this release is the file-first viewer and its documented snapshot-consumption contracts. Internal workspace wiring is not a supported install path.
|
|
33
|
+
- Studio's public package surface in this release is the file-first viewer and its documented snapshot-consumption, filtering, and graph rendering contracts. Internal workspace wiring is not a supported install path.
|
|
34
34
|
|
|
35
35
|
## When to Use
|
|
36
36
|
|
|
@@ -41,7 +41,7 @@ The published package serves two caller-facing entrypoints:
|
|
|
41
41
|
|
|
42
42
|
## Quick Start
|
|
43
43
|
|
|
44
|
-
Studio consumes JSON exports from the fluo CLI.
|
|
44
|
+
Studio consumes JSON exports from the fluo CLI. Runtime produces snapshots, the CLI exports or delegates inspection data, and Studio owns the public helpers that parse, filter, view, and render those snapshots for viewer and automation callers. Supported inspect artifacts include raw snapshots, standalone timing diagnostics, snapshot-plus-timing envelopes, and report artifacts produced by `fluo inspect --report`.
|
|
45
45
|
|
|
46
46
|
1. **Export a snapshot**:
|
|
47
47
|
```bash
|
|
@@ -68,21 +68,24 @@ Use the **Diagnostics** tab to see issues collected during the runtime bootstrap
|
|
|
68
68
|
2. Select the modules or components you want to visualize.
|
|
69
69
|
3. Use the **Export to Mermaid** button to get a text-based diagram for your documentation.
|
|
70
70
|
|
|
71
|
+
For automation, call `renderMermaid(snapshot)` from `@fluojs/studio` or `@fluojs/studio/contracts`. The helper is the supported snapshot-to-Mermaid contract: runtime packages remain snapshot producers, and Studio handles internal dependency edges plus external dependency nodes when rendering the graph.
|
|
72
|
+
|
|
71
73
|
## Public API
|
|
72
74
|
|
|
73
|
-
Studio is primarily a web application, but the published package also exposes the documented snapshot-consumption helpers used by tooling and automation.
|
|
75
|
+
Studio is primarily a web application, but the published package also exposes the documented snapshot-consumption helpers used by tooling and automation. Treat `@fluojs/studio` as the canonical owner of snapshot parsing, filtering, and Mermaid graph rendering semantics.
|
|
74
76
|
|
|
75
77
|
| Contract | Description |
|
|
76
78
|
|---|---|
|
|
77
79
|
| `PlatformShellSnapshot` | The core data structure representing the application state. |
|
|
78
80
|
| `PlatformDiagnosticIssue` | Schema for reporting and fixing platform errors. |
|
|
79
81
|
| `parseStudioPayload(rawJson)` | Validates CLI/exported JSON into the Studio snapshot/timing envelope. |
|
|
82
|
+
| `StudioReportArtifact` | Preserved `fluo inspect --report` artifact with summary, snapshot, and timing data for CI/support automation. |
|
|
80
83
|
| `applyFilters(snapshot, filter)` | Applies readiness/severity/query filters without mutating the source snapshot. |
|
|
81
|
-
| `renderMermaid(snapshot)` | Produces Mermaid graph text from the loaded platform graph. |
|
|
84
|
+
| `renderMermaid(snapshot)` | Produces Mermaid graph text from the loaded platform graph, including internal component dependency edges and external dependency nodes. |
|
|
82
85
|
|
|
83
86
|
### Published package entrypoints
|
|
84
87
|
|
|
85
|
-
- `@fluojs/studio`: root helper barrel for snapshot parsing/filtering/rendering.
|
|
88
|
+
- `@fluojs/studio`: root helper barrel for snapshot parsing/filtering/rendering automation.
|
|
86
89
|
- `@fluojs/studio/contracts`: explicit helper subpath for tooling that wants the contract helpers directly.
|
|
87
90
|
- `@fluojs/studio/viewer`: packaged `dist/index.html` entrypoint for the browser viewer bundle.
|
|
88
91
|
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const a of document.querySelectorAll('link[rel="modulepreload"]'))p(a);new MutationObserver(a=>{for(const o of a)if(o.type==="childList")for(const r of o.addedNodes)r.tagName==="LINK"&&r.rel==="modulepreload"&&p(r)}).observe(document,{childList:!0,subtree:!0});function n(a){const o={};return a.integrity&&(o.integrity=a.integrity),a.referrerPolicy&&(o.referrerPolicy=a.referrerPolicy),a.crossOrigin==="use-credentials"?o.credentials="include":a.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function p(a){if(a.ep)return;a.ep=!0;const o=n(a);fetch(a.href,o)}})();function u(e){return typeof e=="object"&&e!==null}function $(e,t){return Object.hasOwn(e,t)}function L(e){return Array.isArray(e)&&e.every(t=>typeof t=="string")}function E(e){return e==="ready"||e==="not-ready"||e==="degraded"}function x(e){return e==="healthy"||e==="unhealthy"||e==="degraded"}function M(e){return e==="error"||e==="warning"||e==="info"}function j(e){if(!u(e))return null;if(typeof e.generatedAt!="string"||!u(e.readiness)||!u(e.health)||!Array.isArray(e.components)||!Array.isArray(e.diagnostics))throw new Error("Invalid platform snapshot payload.");if(!E(e.readiness.status)||typeof e.readiness.critical!="boolean")throw new Error("Invalid aggregate readiness in platform snapshot payload.");if(!x(e.health.status))throw new Error("Invalid aggregate health in platform snapshot payload.");for(const t of e.components){if(!u(t))throw new Error("Invalid component entry in platform snapshot payload.");if(typeof t.id!="string"||typeof t.kind!="string"||typeof t.state!="string"||!u(t.readiness)||!u(t.health)||!L(t.dependencies)||!u(t.telemetry)||!u(t.ownership)||!u(t.details))throw new Error("Invalid component shape in platform snapshot payload.");if(!E(t.readiness.status)||typeof t.readiness.critical!="boolean")throw new Error("Invalid component readiness in platform snapshot payload.");if(!x(t.health.status))throw new Error("Invalid component health in platform snapshot payload.");if(typeof t.telemetry.namespace!="string"||!u(t.telemetry.tags)||typeof t.ownership.ownsResources!="boolean"||typeof t.ownership.externallyManaged!="boolean")throw new Error("Invalid component telemetry/ownership in platform snapshot payload.")}for(const t of e.diagnostics){if(!u(t))throw new Error("Invalid diagnostics issue entry in platform snapshot payload.");if(typeof t.code!="string"||!M(t.severity)||typeof t.componentId!="string"||typeof t.message!="string")throw new Error("Invalid diagnostics issue shape in platform snapshot payload.");if(t.cause!==void 0&&typeof t.cause!="string"||t.fixHint!==void 0&&typeof t.fixHint!="string"||t.docsUrl!==void 0&&typeof t.docsUrl!="string"||t.dependsOn!==void 0&&!L(t.dependsOn))throw new Error("Invalid optional diagnostics issue fields in platform snapshot payload.")}return e}function k(e){if(!u(e))return null;if(e.version!==1)throw new Error("Unsupported bootstrap timing version. Expected version: 1.");if(typeof e.totalMs!="number"||!Array.isArray(e.phases))throw new Error("Invalid bootstrap timing payload.");for(const t of e.phases)if(!u(t)||typeof t.name!="string"||typeof t.durationMs!="number")throw new Error("Invalid phase entry in bootstrap timing payload.");return e}function T(e){if(!u(e))throw new Error("Invalid inspect report summary payload.");if(typeof e.componentCount!="number"||typeof e.diagnosticCount!="number"||typeof e.errorCount!="number"||!x(e.healthStatus)||!E(e.readinessStatus)||typeof e.timingTotalMs!="number"||typeof e.warningCount!="number")throw new Error("Invalid inspect report summary payload.");return e}function D(e){return e.summary!==void 0||$(e,"snapshot")&&$(e,"timing")&&($(e,"generatedAt")||$(e,"version"))}function J(e,t,n){if(!u(e)||!D(e))return null;if(e.summary===void 0||e.version!==1||typeof e.generatedAt!="string"||!t||!n)throw new Error("Invalid inspect report artifact payload.");return{generatedAt:e.generatedAt,snapshot:t,summary:T(e.summary),timing:n,version:1}}function q(e){const t=JSON.parse(e),n=u(t)?t:void 0,p=n!==void 0&&$(n,"snapshot"),a=n!==void 0&&$(n,"timing"),o=n!==void 0&&!p&&!a&&$(n,"version")&&$(n,"totalMs")&&$(n,"phases"),r=j(p?n.snapshot:o?void 0:t),d=k(a?n.timing:r?void 0:t),l=J(t,r,d);if(!r&&!d)throw new Error("Unsupported file format. Expected platform snapshot JSON or timing JSON.");return{payload:{...l?{report:l}:{},...r?{snapshot:r}:{},...d?{timing:d}:{}},rawJson:e}}function R(e,t){const n=t.query.trim().toLowerCase(),p=e.components.filter(o=>t.readinessStatuses.length>0&&!t.readinessStatuses.includes(o.readiness.status)?!1:n?o.id.toLowerCase().includes(n)||o.kind.toLowerCase().includes(n)||o.dependencies.some(r=>r.toLowerCase().includes(n)):!0),a=e.diagnostics.filter(o=>{var r,d,l,w;return t.severities.length>0&&!t.severities.includes(o.severity)?!1:n?o.code.toLowerCase().includes(n)||o.componentId.toLowerCase().includes(n)||o.message.toLowerCase().includes(n)||(((r=o.cause)==null?void 0:r.toLowerCase().includes(n))??!1)||(((d=o.fixHint)==null?void 0:d.toLowerCase().includes(n))??!1)||(((l=o.docsUrl)==null?void 0:l.toLowerCase().includes(n))??!1)||(((w=o.dependsOn)==null?void 0:w.some(g=>g.toLowerCase().includes(n)))??!1):!0});return{...e,components:p,diagnostics:a}}function b(e){return e.replaceAll('"','\\"')}function U(e){return e.replaceAll(/[^a-zA-Z0-9_]/g,"_")}function P(e){let t=2166136261;for(const n of e)t^=n.codePointAt(0)??0,t=Math.imul(t,16777619)>>>0;return t.toString(16).padStart(8,"0")}function F(e){return`EXT_${U(e)}_${P(e)}`}function O(e){const t=["graph TD"],n=new Map,p=new Map;if(e.components.length===0)return t.push(' EMPTY["No registered platform components"]'),t.join(`
|
|
2
|
+
`);for(const[r,d]of e.components.entries()){const l=`C${String(r+1)}`;n.set(d.id,l),t.push(` ${l}["${b(d.id)}\\nkind: ${b(d.kind)}\\nreadiness: ${d.readiness.status}\\nhealth: ${d.health.status}"]`)}for(const r of e.components){const d=n.get(r.id);if(d)for(const l of r.dependencies){const w=n.get(l);if(w){t.push(` ${d} --> ${w}`);continue}let g=p.get(l);g||(g=F(l),p.set(l,g),t.push(` ${g}["${b(l)}"]`)),t.push(` ${d} --> ${g}`)}}const a=[],o=[];for(const r of e.components){const d=n.get(r.id);d&&(r.readiness.status==="degraded"&&a.push(d),r.readiness.status==="not-ready"&&o.push(d))}return a.length>0&&(t.push(` class ${a.join(",")} degraded`),t.push(" classDef degraded stroke:#f59e0b,stroke-width:2px")),o.length>0&&(t.push(` class ${o.join(",")} notReady`),t.push(" classDef notReady stroke:#ef4444,stroke-width:2px")),t.join(`
|
|
3
|
+
`)}const N=document.querySelector("#app");if(!N)throw new Error("App root not found.");const H=N,_=["ready","degraded","not-ready"],z=["error","warning","info"],i={filter:{query:"",readinessStatuses:[],severities:[]}};function c(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}function Y(e,t){const n=new Blob([t],{type:"application/json"}),p=URL.createObjectURL(n),a=document.createElement("a");a.href=p,a.download=e,a.click(),URL.revokeObjectURL(p)}async function A(e){if(!navigator.clipboard)throw new Error("Clipboard API is unavailable.");await navigator.clipboard.writeText(e)}function C(e,t){return e.includes(t)?e.filter(n=>n!==t):[...e,t]}function I(e,t){return!e||e.components.length===0?void 0:(t?e.components.find(p=>p.id===t):void 0)??e.components[0]}function S(){var n;const e=(n=i.payload)==null?void 0:n.snapshot;if(!e){i.filteredSnapshot=void 0,i.selectedComponentId=void 0;return}i.filteredSnapshot=R(e,i.filter);const t=I(i.filteredSnapshot,i.selectedComponentId);i.selectedComponentId=t==null?void 0:t.id}function X(e,t){const a=Math.min(900,460)/2-70,o=900/2,r=460/2,d=e.components,l=new Map;d.forEach((m,s)=>{const f=Math.PI*2*s/Math.max(d.length,1);l.set(m.id,{x:o+a*Math.cos(f),y:r+a*Math.sin(f)})});const w=d.flatMap(m=>m.dependencies.map(s=>{const f=l.get(m.id),h=l.get(s);return!f||!h?"":`<line x1="${f.x}" y1="${f.y}" x2="${h.x}" y2="${h.y}" class="edge-line" marker-end="url(#arrow)" />`})).join(""),g=d.map(m=>{const s=l.get(m.id);if(!s)return"";const h=["module-node",m.readiness.status==="not-ready"?"component-not-ready":m.readiness.status==="degraded"?"component-degraded":"component-ready",m.id===t?"module-selected":""].filter(Boolean).join(" ");return`<g>
|
|
4
|
+
<circle cx="${s.x}" cy="${s.y}" r="34" class="${h}" data-component="${c(m.id)}" />
|
|
5
|
+
<text x="${s.x}" y="${s.y+4}" text-anchor="middle" class="module-label">${c(m.id)}</text>
|
|
6
|
+
</g>`}).join("");return`<svg viewBox="0 0 900 460" role="img" aria-label="Platform component dependency graph">
|
|
7
|
+
<defs>
|
|
8
|
+
<marker id="arrow" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
|
9
|
+
<polygon points="0 0, 10 3.5, 0 7" class="edge-arrow" />
|
|
10
|
+
</marker>
|
|
11
|
+
</defs>
|
|
12
|
+
${w}
|
|
13
|
+
${g}
|
|
14
|
+
</svg>`}function B(e){if(!e)return'<p class="muted">No component selected.</p>';const t=e.dependencies.length>0?e.dependencies.map(n=>`<span class="chip">dependsOn: ${c(n)}</span>`).join(""):'<span class="chip">dependsOn: none</span>';return`
|
|
15
|
+
<h3>${c(e.id)}</h3>
|
|
16
|
+
<p class="muted">kind: <strong>${c(e.kind)}</strong> · state: <strong>${c(e.state)}</strong></p>
|
|
17
|
+
<div class="chips">
|
|
18
|
+
<span class="chip">readiness: ${c(e.readiness.status)}</span>
|
|
19
|
+
<span class="chip">critical: ${e.readiness.critical?"true":"false"}</span>
|
|
20
|
+
<span class="chip">health: ${c(e.health.status)}</span>
|
|
21
|
+
<span class="chip">ownership: owns=${e.ownership.ownsResources?"true":"false"}/external=${e.ownership.externallyManaged?"true":"false"}</span>
|
|
22
|
+
${t}
|
|
23
|
+
</div>
|
|
24
|
+
<p class="muted">telemetry namespace: <code>${c(e.telemetry.namespace)}</code></p>
|
|
25
|
+
<h4>Sanitized details</h4>
|
|
26
|
+
<pre>${c(JSON.stringify(e.details,null,2))}</pre>
|
|
27
|
+
`}function V(){var t;const e=(t=i.payload)==null?void 0:t.timing;return e?`
|
|
28
|
+
<p><strong>Total:</strong> ${e.totalMs.toFixed(3)}ms</p>
|
|
29
|
+
<table>
|
|
30
|
+
<thead>
|
|
31
|
+
<tr><th>phase</th><th>duration (ms)</th></tr>
|
|
32
|
+
</thead>
|
|
33
|
+
<tbody>
|
|
34
|
+
${e.phases.map(n=>`<tr><td>${c(n.name)}</td><td>${n.durationMs.toFixed(3)}</td></tr>`).join("")}
|
|
35
|
+
</tbody>
|
|
36
|
+
</table>
|
|
37
|
+
`:'<p class="muted">Timing not collected.</p>'}function G(e){if(!e)return'<p class="muted">No platform snapshot loaded.</p>';const t={degraded:e.components.filter(n=>n.readiness.status==="degraded").length,notReady:e.components.filter(n=>n.readiness.status==="not-ready").length,ready:e.components.filter(n=>n.readiness.status==="ready").length};return`
|
|
38
|
+
<div class="chips">
|
|
39
|
+
<span class="chip">generatedAt: ${c(e.generatedAt)}</span>
|
|
40
|
+
<span class="chip">aggregate readiness: ${c(e.readiness.status)}</span>
|
|
41
|
+
<span class="chip">aggregate health: ${c(e.health.status)}</span>
|
|
42
|
+
<span class="chip">components: ${String(e.components.length)}</span>
|
|
43
|
+
<span class="chip">diagnostics: ${String(e.diagnostics.length)}</span>
|
|
44
|
+
<span class="chip">ready/degraded/not-ready: ${t.ready}/${t.degraded}/${t.notReady}</span>
|
|
45
|
+
</div>
|
|
46
|
+
`}function K(e){return e?e.diagnostics.length===0?'<p class="muted">No diagnostics issues.</p>':`<div class="diagnostics-list">
|
|
47
|
+
${e.diagnostics.map(t=>{const n=t.dependsOn&&t.dependsOn.length>0?`<div class="chips">${t.dependsOn.map(p=>`<span class="chip">dependsOn: ${c(p)}</span>`).join("")}</div>`:"";return`<article class="card issue severity-${c(t.severity)}">
|
|
48
|
+
<h3>${c(t.code)}</h3>
|
|
49
|
+
<p><strong>severity:</strong> ${c(t.severity)} · <strong>component:</strong> ${c(t.componentId)}</p>
|
|
50
|
+
<p>${c(t.message)}</p>
|
|
51
|
+
${t.cause?`<p><strong>cause:</strong> ${c(t.cause)}</p>`:""}
|
|
52
|
+
${t.fixHint?`<p><strong>fix hint:</strong> ${c(t.fixHint)}</p>`:""}
|
|
53
|
+
${t.docsUrl?`<p><strong>docs:</strong> <a href="${c(t.docsUrl)}" target="_blank" rel="noreferrer">${c(t.docsUrl)}</a></p>`:""}
|
|
54
|
+
${n}
|
|
55
|
+
</article>`}).join("")}
|
|
56
|
+
</div>`:'<p class="muted">No platform snapshot loaded.</p>'}function y(e){const t=i.filteredSnapshot,n=I(t,i.selectedComponentId),p=t?O(t):"",a=t&&t.components.length>0?X(t,n==null?void 0:n.id):'<p class="muted">No platform components loaded.</p>';H.innerHTML=`
|
|
57
|
+
<main>
|
|
58
|
+
<header>
|
|
59
|
+
<h1>Fluo Studio Platform Snapshot Viewer</h1>
|
|
60
|
+
<p>Load JSON exported by <code>fluo inspect --json</code> (shared platform snapshot/diagnostic schema) and optionally timing JSON from <code>--timing</code>.</p>
|
|
61
|
+
</header>
|
|
62
|
+
|
|
63
|
+
<section class="card uploader" id="drop-zone">
|
|
64
|
+
<h2>Diagnostics file input</h2>
|
|
65
|
+
<p>Drag & drop a JSON file, or choose one manually.</p>
|
|
66
|
+
<input type="file" id="file-input" accept="application/json" />
|
|
67
|
+
<div class="actions">
|
|
68
|
+
<button id="download-json" ${i.rawJson?"":"disabled"}>Download loaded JSON</button>
|
|
69
|
+
<button id="copy-json" ${i.rawJson?"":"disabled"}>Copy loaded JSON</button>
|
|
70
|
+
<button id="copy-mermaid" ${t?"":"disabled"}>Copy Mermaid</button>
|
|
71
|
+
</div>
|
|
72
|
+
${e?`<p class="notice">${c(e)}</p>`:""}
|
|
73
|
+
</section>
|
|
74
|
+
|
|
75
|
+
<section class="card">
|
|
76
|
+
<h2>Snapshot summary</h2>
|
|
77
|
+
${G(t)}
|
|
78
|
+
</section>
|
|
79
|
+
|
|
80
|
+
<section class="split-grid">
|
|
81
|
+
<div class="card">
|
|
82
|
+
<h2>Search and filtering</h2>
|
|
83
|
+
<label>
|
|
84
|
+
Search component/diagnostic
|
|
85
|
+
<input type="text" id="search" value="${c(i.filter.query)}" placeholder="e.g. redis.default or QUEUE_DEPENDENCY_NOT_READY" />
|
|
86
|
+
</label>
|
|
87
|
+
|
|
88
|
+
<div class="filter-row">
|
|
89
|
+
<span>Component readiness</span>
|
|
90
|
+
${_.map(s=>`<label><input type="checkbox" data-readiness="${s}" ${i.filter.readinessStatuses.includes(s)?"checked":""}/> ${s}</label>`).join("")}
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div class="filter-row">
|
|
94
|
+
<span>Diagnostic severity</span>
|
|
95
|
+
${z.map(s=>`<label><input type="checkbox" data-severity="${s}" ${i.filter.severities.includes(s)?"checked":""}/> ${s}</label>`).join("")}
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<div class="card">
|
|
100
|
+
<h2>Timing</h2>
|
|
101
|
+
${V()}
|
|
102
|
+
</div>
|
|
103
|
+
</section>
|
|
104
|
+
|
|
105
|
+
<section class="card">
|
|
106
|
+
<h2>Platform dependency graph</h2>
|
|
107
|
+
<p class="muted">Component dependencies are rendered directly from the shared platform snapshot schema.</p>
|
|
108
|
+
<div id="graph-host">${a}</div>
|
|
109
|
+
</section>
|
|
110
|
+
|
|
111
|
+
<section class="split-grid">
|
|
112
|
+
<div class="card" id="details-panel">
|
|
113
|
+
<h2>Component details</h2>
|
|
114
|
+
${B(n)}
|
|
115
|
+
</div>
|
|
116
|
+
<div class="card">
|
|
117
|
+
<h2>Mermaid output</h2>
|
|
118
|
+
<pre>${c(p||"No snapshot loaded.")}</pre>
|
|
119
|
+
</div>
|
|
120
|
+
</section>
|
|
121
|
+
|
|
122
|
+
<section class="card">
|
|
123
|
+
<h2>Diagnostics issues</h2>
|
|
124
|
+
<p class="muted">Fix hints and dependency chains are rendered from <code>diagnostics.fixHint</code> and <code>diagnostics.dependsOn</code>.</p>
|
|
125
|
+
${K(t)}
|
|
126
|
+
</section>
|
|
127
|
+
</main>
|
|
128
|
+
`;const o=document.querySelector("#file-input"),r=document.querySelector("#drop-zone"),d=document.querySelector("#search"),l=document.querySelector("#copy-json"),w=document.querySelector("#download-json"),g=document.querySelector("#copy-mermaid"),m=async s=>{const f=await s.text();try{const h=q(f);i.payload=h.payload,i.rawJson=h.rawJson,S(),y("Diagnostics file loaded successfully.")}catch(h){i.payload=void 0,i.filteredSnapshot=void 0,i.selectedComponentId=void 0,i.rawJson=void 0,y(h instanceof Error?h.message:"Failed to parse diagnostics file.")}};o==null||o.addEventListener("change",async s=>{var v;const h=(v=s.target.files)==null?void 0:v[0];h&&await m(h)}),r==null||r.addEventListener("dragover",s=>{s.preventDefault(),r.classList.add("drag-active")}),r==null||r.addEventListener("dragleave",()=>{r.classList.remove("drag-active")}),r==null||r.addEventListener("drop",async s=>{var h,v;s.preventDefault(),r.classList.remove("drag-active");const f=(v=(h=s.dataTransfer)==null?void 0:h.files)==null?void 0:v[0];f&&await m(f)}),d==null||d.addEventListener("input",s=>{const f=s.target;i.filter.query=f.value,S(),y()}),document.querySelectorAll("input[data-readiness]").forEach(s=>{s.addEventListener("change",()=>{i.filter.readinessStatuses=C(i.filter.readinessStatuses,s.dataset.readiness),S(),y()})}),document.querySelectorAll("input[data-severity]").forEach(s=>{s.addEventListener("change",()=>{i.filter.severities=C(i.filter.severities,s.dataset.severity),S(),y()})}),l==null||l.addEventListener("click",async()=>{if(i.rawJson)try{await A(i.rawJson),y("Loaded JSON copied to clipboard.")}catch(s){y(s instanceof Error?s.message:"Failed to copy JSON.")}}),w==null||w.addEventListener("click",()=>{i.rawJson&&(Y("fluo-diagnostics.json",i.rawJson),y("Loaded JSON downloaded."))}),g==null||g.addEventListener("click",async()=>{if(t)try{await A(O(t)),y("Mermaid copied to clipboard.")}catch(s){y(s instanceof Error?s.message:"Failed to copy Mermaid text.")}}),document.querySelectorAll("[data-component]").forEach(s=>{s.addEventListener("click",()=>{const f=s.dataset.component;f&&(i.selectedComponentId=f,y())})})}S();y();
|
package/dist/contracts.d.ts
CHANGED
|
@@ -1,10 +1,40 @@
|
|
|
1
1
|
import type { BootstrapTimingDiagnostics, PlatformDiagnosticIssue, PlatformShellSnapshot, PlatformSnapshot } from '@fluojs/runtime';
|
|
2
|
+
export type { PlatformDiagnosticIssue, PlatformShellSnapshot } from '@fluojs/runtime';
|
|
3
|
+
/**
|
|
4
|
+
* Readiness statuses supported by Studio snapshot filtering and graph annotations.
|
|
5
|
+
*/
|
|
2
6
|
export type PlatformReadinessStatus = PlatformSnapshot['readiness']['status'];
|
|
7
|
+
/**
|
|
8
|
+
* Diagnostic severities supported by Studio snapshot filtering.
|
|
9
|
+
*/
|
|
3
10
|
export type PlatformDiagnosticSeverity = PlatformDiagnosticIssue['severity'];
|
|
11
|
+
/**
|
|
12
|
+
* Stable summary emitted by `fluo inspect --report` for support and CI triage.
|
|
13
|
+
*/
|
|
14
|
+
export interface StudioReportSummary {
|
|
15
|
+
componentCount: number;
|
|
16
|
+
diagnosticCount: number;
|
|
17
|
+
errorCount: number;
|
|
18
|
+
healthStatus: PlatformShellSnapshot['health']['status'];
|
|
19
|
+
readinessStatus: PlatformShellSnapshot['readiness']['status'];
|
|
20
|
+
timingTotalMs: number;
|
|
21
|
+
warningCount: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* CI-friendly report artifact emitted by `fluo inspect --report`.
|
|
25
|
+
*/
|
|
26
|
+
export interface StudioReportArtifact {
|
|
27
|
+
generatedAt: string;
|
|
28
|
+
snapshot: PlatformShellSnapshot;
|
|
29
|
+
summary: StudioReportSummary;
|
|
30
|
+
timing: BootstrapTimingDiagnostics;
|
|
31
|
+
version: 1;
|
|
32
|
+
}
|
|
4
33
|
/**
|
|
5
34
|
* Serializable Studio payload envelope built from inspect snapshot/timing exports.
|
|
6
35
|
*/
|
|
7
36
|
export interface StudioPayload {
|
|
37
|
+
report?: StudioReportArtifact;
|
|
8
38
|
snapshot?: PlatformShellSnapshot;
|
|
9
39
|
timing?: BootstrapTimingDiagnostics;
|
|
10
40
|
}
|
|
@@ -42,6 +72,11 @@ export declare function applyFilters(snapshot: PlatformShellSnapshot, filter: Fi
|
|
|
42
72
|
/**
|
|
43
73
|
* Renders the loaded platform snapshot as a Mermaid dependency graph.
|
|
44
74
|
*
|
|
75
|
+
* @remarks
|
|
76
|
+
* `@fluojs/studio` owns the snapshot consumption and graph rendering contract. Runtime packages remain the
|
|
77
|
+
* snapshot producers, while automation and viewer callers use this helper to turn a loaded snapshot into a
|
|
78
|
+
* stable Mermaid graph.
|
|
79
|
+
*
|
|
45
80
|
* @param snapshot - The platform snapshot to render.
|
|
46
81
|
* @returns Mermaid graph text suitable for docs or clipboard export.
|
|
47
82
|
*/
|
package/dist/contracts.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,0BAA0B,EAC1B,uBAAuB,EACvB,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,0BAA0B,EAC1B,uBAAuB,EACvB,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAEtF;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,CAAC;AAE9E;;GAEG;AACH,MAAM,MAAM,0BAA0B,GAAG,uBAAuB,CAAC,UAAU,CAAC,CAAC;AAE7E;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,qBAAqB,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAC;IACxD,eAAe,EAAE,qBAAqB,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC9D,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,qBAAqB,CAAC;IAChC,OAAO,EAAE,mBAAmB,CAAC;IAC7B,MAAM,EAAE,0BAA0B,CAAC;IACnC,OAAO,EAAE,CAAC,CAAC;CACZ;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,MAAM,CAAC,EAAE,0BAA0B,CAAC;CACrC;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,EAAE,uBAAuB,EAAE,CAAC;IAC7C,UAAU,EAAE,0BAA0B,EAAE,CAAC;CAC1C;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,aAAa,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB;AAkLD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,CA4BjE;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,qBAAqB,EAAE,MAAM,EAAE,WAAW,GAAG,qBAAqB,CAwCxG;AAyBD;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,CAsErE"}
|
package/dist/contracts.js
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Readiness statuses supported by Studio snapshot filtering and graph annotations.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Diagnostic severities supported by Studio snapshot filtering.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Stable summary emitted by `fluo inspect --report` for support and CI triage.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* CI-friendly report artifact emitted by `fluo inspect --report`.
|
|
15
|
+
*/
|
|
16
|
+
|
|
1
17
|
/**
|
|
2
18
|
* Serializable Studio payload envelope built from inspect snapshot/timing exports.
|
|
3
19
|
*/
|
|
@@ -13,6 +29,9 @@
|
|
|
13
29
|
function isRecord(value) {
|
|
14
30
|
return typeof value === 'object' && value !== null;
|
|
15
31
|
}
|
|
32
|
+
function hasOwn(value, key) {
|
|
33
|
+
return Object.hasOwn(value, key);
|
|
34
|
+
}
|
|
16
35
|
function isStringArray(value) {
|
|
17
36
|
return Array.isArray(value) && value.every(entry => typeof entry === 'string');
|
|
18
37
|
}
|
|
@@ -85,6 +104,33 @@ function validateTiming(value) {
|
|
|
85
104
|
}
|
|
86
105
|
return value;
|
|
87
106
|
}
|
|
107
|
+
function validateReportSummary(value) {
|
|
108
|
+
if (!isRecord(value)) {
|
|
109
|
+
throw new Error('Invalid inspect report summary payload.');
|
|
110
|
+
}
|
|
111
|
+
if (typeof value.componentCount !== 'number' || typeof value.diagnosticCount !== 'number' || typeof value.errorCount !== 'number' || !isHealthStatus(value.healthStatus) || !isReadinessStatus(value.readinessStatus) || typeof value.timingTotalMs !== 'number' || typeof value.warningCount !== 'number') {
|
|
112
|
+
throw new Error('Invalid inspect report summary payload.');
|
|
113
|
+
}
|
|
114
|
+
return value;
|
|
115
|
+
}
|
|
116
|
+
function isReportArtifactEnvelope(value) {
|
|
117
|
+
return value.summary !== undefined || hasOwn(value, 'snapshot') && hasOwn(value, 'timing') && (hasOwn(value, 'generatedAt') || hasOwn(value, 'version'));
|
|
118
|
+
}
|
|
119
|
+
function validateReport(value, snapshot, timing) {
|
|
120
|
+
if (!isRecord(value) || !isReportArtifactEnvelope(value)) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
if (value.summary === undefined || value.version !== 1 || typeof value.generatedAt !== 'string' || !snapshot || !timing) {
|
|
124
|
+
throw new Error('Invalid inspect report artifact payload.');
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
generatedAt: value.generatedAt,
|
|
128
|
+
snapshot,
|
|
129
|
+
summary: validateReportSummary(value.summary),
|
|
130
|
+
timing,
|
|
131
|
+
version: 1
|
|
132
|
+
};
|
|
133
|
+
}
|
|
88
134
|
|
|
89
135
|
/**
|
|
90
136
|
* Parses a Studio JSON file into the documented snapshot/timing envelope.
|
|
@@ -96,13 +142,20 @@ function validateTiming(value) {
|
|
|
96
142
|
export function parseStudioPayload(rawJson) {
|
|
97
143
|
const parsed = JSON.parse(rawJson);
|
|
98
144
|
const envelope = isRecord(parsed) ? parsed : undefined;
|
|
99
|
-
const
|
|
100
|
-
const
|
|
145
|
+
const hasSnapshotEnvelope = envelope !== undefined && hasOwn(envelope, 'snapshot');
|
|
146
|
+
const hasTimingEnvelope = envelope !== undefined && hasOwn(envelope, 'timing');
|
|
147
|
+
const standaloneTiming = envelope !== undefined && !hasSnapshotEnvelope && !hasTimingEnvelope && hasOwn(envelope, 'version') && hasOwn(envelope, 'totalMs') && hasOwn(envelope, 'phases');
|
|
148
|
+
const snapshot = validateSnapshot(hasSnapshotEnvelope ? envelope.snapshot : standaloneTiming ? undefined : parsed);
|
|
149
|
+
const timing = validateTiming(hasTimingEnvelope ? envelope.timing : !snapshot ? parsed : undefined);
|
|
150
|
+
const report = validateReport(parsed, snapshot, timing);
|
|
101
151
|
if (!snapshot && !timing) {
|
|
102
152
|
throw new Error('Unsupported file format. Expected platform snapshot JSON or timing JSON.');
|
|
103
153
|
}
|
|
104
154
|
return {
|
|
105
155
|
payload: {
|
|
156
|
+
...(report ? {
|
|
157
|
+
report
|
|
158
|
+
} : {}),
|
|
106
159
|
...(snapshot ? {
|
|
107
160
|
snapshot
|
|
108
161
|
} : {}),
|
|
@@ -168,6 +221,11 @@ function createExternalMermaidNodeId(value) {
|
|
|
168
221
|
/**
|
|
169
222
|
* Renders the loaded platform snapshot as a Mermaid dependency graph.
|
|
170
223
|
*
|
|
224
|
+
* @remarks
|
|
225
|
+
* `@fluojs/studio` owns the snapshot consumption and graph rendering contract. Runtime packages remain the
|
|
226
|
+
* snapshot producers, while automation and viewer callers use this helper to turn a loaded snapshot into a
|
|
227
|
+
* stable Mermaid graph.
|
|
228
|
+
*
|
|
171
229
|
* @param snapshot - The platform snapshot to render.
|
|
172
230
|
* @returns Mermaid graph text suitable for docs or clipboard export.
|
|
173
231
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { applyFilters, parseStudioPayload, renderMermaid, type FilterState, type ParsedPayload, type PlatformDiagnosticSeverity, type PlatformReadinessStatus, type StudioPayload, } from './contracts.js';
|
|
1
|
+
export { applyFilters, parseStudioPayload, renderMermaid, type FilterState, type ParsedPayload, type PlatformDiagnosticIssue, type PlatformDiagnosticSeverity, type PlatformReadinessStatus, type StudioReportArtifact, type StudioReportSummary, type PlatformShellSnapshot, type StudioPayload, } from './contracts.js';
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,aAAa,EACb,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,0BAA0B,EAC/B,KAAK,uBAAuB,EAC5B,KAAK,aAAa,GACnB,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,aAAa,EACb,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,uBAAuB,EAC5B,KAAK,0BAA0B,EAC/B,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,aAAa,GACnB,MAAM,gBAAgB,CAAC"}
|
package/dist/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Fluo Studio</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-D-3gdi9Y.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/assets/index-BTqPuJus.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"module-graph",
|
|
10
10
|
"devtools"
|
|
11
11
|
],
|
|
12
|
-
"version": "1.0.0-beta.
|
|
12
|
+
"version": "1.0.0-beta.3",
|
|
13
13
|
"private": false,
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"repository": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"dist"
|
|
42
42
|
],
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@fluojs/runtime": "^1.0.0-beta.
|
|
44
|
+
"@fluojs/runtime": "^1.0.0-beta.2"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"vitest": "^3.2.4",
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))l(o);new MutationObserver(o=>{for(const r of o)if(r.type==="childList")for(const a of r.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&l(a)}).observe(document,{childList:!0,subtree:!0});function n(o){const r={};return o.integrity&&(r.integrity=o.integrity),o.referrerPolicy&&(r.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?r.credentials="include":o.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function l(o){if(o.ep)return;o.ep=!0;const r=n(o);fetch(o.href,r)}})();function g(t){return typeof t=="object"&&t!==null}function b(t){return Array.isArray(t)&&t.every(e=>typeof e=="string")}function E(t){return t==="ready"||t==="not-ready"||t==="degraded"}function x(t){return t==="healthy"||t==="unhealthy"||t==="degraded"}function I(t){return t==="error"||t==="warning"||t==="info"}function M(t){if(!g(t))return null;if(typeof t.generatedAt!="string"||!g(t.readiness)||!g(t.health)||!Array.isArray(t.components)||!Array.isArray(t.diagnostics))throw new Error("Invalid platform snapshot payload.");if(!E(t.readiness.status)||typeof t.readiness.critical!="boolean")throw new Error("Invalid aggregate readiness in platform snapshot payload.");if(!x(t.health.status))throw new Error("Invalid aggregate health in platform snapshot payload.");for(const e of t.components){if(!g(e))throw new Error("Invalid component entry in platform snapshot payload.");if(typeof e.id!="string"||typeof e.kind!="string"||typeof e.state!="string"||!g(e.readiness)||!g(e.health)||!b(e.dependencies)||!g(e.telemetry)||!g(e.ownership)||!g(e.details))throw new Error("Invalid component shape in platform snapshot payload.");if(!E(e.readiness.status)||typeof e.readiness.critical!="boolean")throw new Error("Invalid component readiness in platform snapshot payload.");if(!x(e.health.status))throw new Error("Invalid component health in platform snapshot payload.");if(typeof e.telemetry.namespace!="string"||!g(e.telemetry.tags)||typeof e.ownership.ownsResources!="boolean"||typeof e.ownership.externallyManaged!="boolean")throw new Error("Invalid component telemetry/ownership in platform snapshot payload.")}for(const e of t.diagnostics){if(!g(e))throw new Error("Invalid diagnostics issue entry in platform snapshot payload.");if(typeof e.code!="string"||!I(e.severity)||typeof e.componentId!="string"||typeof e.message!="string")throw new Error("Invalid diagnostics issue shape in platform snapshot payload.");if(e.cause!==void 0&&typeof e.cause!="string"||e.fixHint!==void 0&&typeof e.fixHint!="string"||e.docsUrl!==void 0&&typeof e.docsUrl!="string"||e.dependsOn!==void 0&&!b(e.dependsOn))throw new Error("Invalid optional diagnostics issue fields in platform snapshot payload.")}return t}function j(t){if(!g(t))return null;if(t.version!==1)throw new Error("Unsupported bootstrap timing version. Expected version: 1.");if(typeof t.totalMs!="number"||!Array.isArray(t.phases))throw new Error("Invalid bootstrap timing payload.");for(const e of t.phases)if(!g(e)||typeof e.name!="string"||typeof e.durationMs!="number")throw new Error("Invalid phase entry in bootstrap timing payload.");return t}function k(t){const e=JSON.parse(t),n=g(e)?e:void 0,l=M((n==null?void 0:n.snapshot)??e),o=j((n==null?void 0:n.timing)??(l?void 0:e));if(!l&&!o)throw new Error("Unsupported file format. Expected platform snapshot JSON or timing JSON.");return{payload:{...l?{snapshot:l}:{},...o?{timing:o}:{}},rawJson:t}}function D(t,e){const n=e.query.trim().toLowerCase(),l=t.components.filter(r=>e.readinessStatuses.length>0&&!e.readinessStatuses.includes(r.readiness.status)?!1:n?r.id.toLowerCase().includes(n)||r.kind.toLowerCase().includes(n)||r.dependencies.some(a=>a.toLowerCase().includes(n)):!0),o=t.diagnostics.filter(r=>{var a,c,p,w;return e.severities.length>0&&!e.severities.includes(r.severity)?!1:n?r.code.toLowerCase().includes(n)||r.componentId.toLowerCase().includes(n)||r.message.toLowerCase().includes(n)||(((a=r.cause)==null?void 0:a.toLowerCase().includes(n))??!1)||(((c=r.fixHint)==null?void 0:c.toLowerCase().includes(n))??!1)||(((p=r.docsUrl)==null?void 0:p.toLowerCase().includes(n))??!1)||(((w=r.dependsOn)==null?void 0:w.some(m=>m.toLowerCase().includes(n)))??!1):!0});return{...t,components:l,diagnostics:o}}function S(t){return t.replaceAll('"','\\"')}function J(t){return t.replaceAll(/[^a-zA-Z0-9_]/g,"_")}function q(t){let e=2166136261;for(const n of t)e^=n.codePointAt(0)??0,e=Math.imul(e,16777619)>>>0;return e.toString(16).padStart(8,"0")}function T(t){return`EXT_${J(t)}_${q(t)}`}function L(t){const e=["graph TD"],n=new Map,l=new Map;if(t.components.length===0)return e.push(' EMPTY["No registered platform components"]'),e.join(`
|
|
2
|
-
`);for(const[a,c]of t.components.entries()){const p=`C${String(a+1)}`;n.set(c.id,p),e.push(` ${p}["${S(c.id)}\\nkind: ${S(c.kind)}\\nreadiness: ${c.readiness.status}\\nhealth: ${c.health.status}"]`)}for(const a of t.components){const c=n.get(a.id);if(c)for(const p of a.dependencies){const w=n.get(p);if(w){e.push(` ${c} --> ${w}`);continue}let m=l.get(p);m||(m=T(p),l.set(p,m),e.push(` ${m}["${S(p)}"]`)),e.push(` ${c} --> ${m}`)}}const o=[],r=[];for(const a of t.components){const c=n.get(a.id);c&&(a.readiness.status==="degraded"&&o.push(c),a.readiness.status==="not-ready"&&r.push(c))}return o.length>0&&(e.push(` class ${o.join(",")} degraded`),e.push(" classDef degraded stroke:#f59e0b,stroke-width:2px")),r.length>0&&(e.push(` class ${r.join(",")} notReady`),e.push(" classDef notReady stroke:#ef4444,stroke-width:2px")),e.join(`
|
|
3
|
-
`)}const C=document.querySelector("#app");if(!C)throw new Error("App root not found.");const R=C,U=["ready","degraded","not-ready"],P=["error","warning","info"],i={filter:{query:"",readinessStatuses:[],severities:[]}};function d(t){return t.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}function F(t,e){const n=new Blob([e],{type:"application/json"}),l=URL.createObjectURL(n),o=document.createElement("a");o.href=l,o.download=t,o.click(),URL.revokeObjectURL(l)}async function O(t){if(!navigator.clipboard)throw new Error("Clipboard API is unavailable.");await navigator.clipboard.writeText(t)}function N(t,e){return t.includes(e)?t.filter(n=>n!==e):[...t,e]}function A(t,e){return!t||t.components.length===0?void 0:(e?t.components.find(l=>l.id===e):void 0)??t.components[0]}function v(){var n;const t=(n=i.payload)==null?void 0:n.snapshot;if(!t){i.filteredSnapshot=void 0,i.selectedComponentId=void 0;return}i.filteredSnapshot=D(t,i.filter);const e=A(i.filteredSnapshot,i.selectedComponentId);i.selectedComponentId=e==null?void 0:e.id}function H(t,e){const o=Math.min(900,460)/2-70,r=900/2,a=460/2,c=t.components,p=new Map;c.forEach((u,s)=>{const f=Math.PI*2*s/Math.max(c.length,1);p.set(u.id,{x:r+o*Math.cos(f),y:a+o*Math.sin(f)})});const w=c.flatMap(u=>u.dependencies.map(s=>{const f=p.get(u.id),h=p.get(s);return!f||!h?"":`<line x1="${f.x}" y1="${f.y}" x2="${h.x}" y2="${h.y}" class="edge-line" marker-end="url(#arrow)" />`})).join(""),m=c.map(u=>{const s=p.get(u.id);if(!s)return"";const h=["module-node",u.readiness.status==="not-ready"?"component-not-ready":u.readiness.status==="degraded"?"component-degraded":"component-ready",u.id===e?"module-selected":""].filter(Boolean).join(" ");return`<g>
|
|
4
|
-
<circle cx="${s.x}" cy="${s.y}" r="34" class="${h}" data-component="${d(u.id)}" />
|
|
5
|
-
<text x="${s.x}" y="${s.y+4}" text-anchor="middle" class="module-label">${d(u.id)}</text>
|
|
6
|
-
</g>`}).join("");return`<svg viewBox="0 0 900 460" role="img" aria-label="Platform component dependency graph">
|
|
7
|
-
<defs>
|
|
8
|
-
<marker id="arrow" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
|
9
|
-
<polygon points="0 0, 10 3.5, 0 7" class="edge-arrow" />
|
|
10
|
-
</marker>
|
|
11
|
-
</defs>
|
|
12
|
-
${w}
|
|
13
|
-
${m}
|
|
14
|
-
</svg>`}function _(t){if(!t)return'<p class="muted">No component selected.</p>';const e=t.dependencies.length>0?t.dependencies.map(n=>`<span class="chip">dependsOn: ${d(n)}</span>`).join(""):'<span class="chip">dependsOn: none</span>';return`
|
|
15
|
-
<h3>${d(t.id)}</h3>
|
|
16
|
-
<p class="muted">kind: <strong>${d(t.kind)}</strong> · state: <strong>${d(t.state)}</strong></p>
|
|
17
|
-
<div class="chips">
|
|
18
|
-
<span class="chip">readiness: ${d(t.readiness.status)}</span>
|
|
19
|
-
<span class="chip">critical: ${t.readiness.critical?"true":"false"}</span>
|
|
20
|
-
<span class="chip">health: ${d(t.health.status)}</span>
|
|
21
|
-
<span class="chip">ownership: owns=${t.ownership.ownsResources?"true":"false"}/external=${t.ownership.externallyManaged?"true":"false"}</span>
|
|
22
|
-
${e}
|
|
23
|
-
</div>
|
|
24
|
-
<p class="muted">telemetry namespace: <code>${d(t.telemetry.namespace)}</code></p>
|
|
25
|
-
<h4>Sanitized details</h4>
|
|
26
|
-
<pre>${d(JSON.stringify(t.details,null,2))}</pre>
|
|
27
|
-
`}function z(){var e;const t=(e=i.payload)==null?void 0:e.timing;return t?`
|
|
28
|
-
<p><strong>Total:</strong> ${t.totalMs.toFixed(3)}ms</p>
|
|
29
|
-
<table>
|
|
30
|
-
<thead>
|
|
31
|
-
<tr><th>phase</th><th>duration (ms)</th></tr>
|
|
32
|
-
</thead>
|
|
33
|
-
<tbody>
|
|
34
|
-
${t.phases.map(n=>`<tr><td>${d(n.name)}</td><td>${n.durationMs.toFixed(3)}</td></tr>`).join("")}
|
|
35
|
-
</tbody>
|
|
36
|
-
</table>
|
|
37
|
-
`:'<p class="muted">Timing not collected.</p>'}function Y(t){if(!t)return'<p class="muted">No platform snapshot loaded.</p>';const e={degraded:t.components.filter(n=>n.readiness.status==="degraded").length,notReady:t.components.filter(n=>n.readiness.status==="not-ready").length,ready:t.components.filter(n=>n.readiness.status==="ready").length};return`
|
|
38
|
-
<div class="chips">
|
|
39
|
-
<span class="chip">generatedAt: ${d(t.generatedAt)}</span>
|
|
40
|
-
<span class="chip">aggregate readiness: ${d(t.readiness.status)}</span>
|
|
41
|
-
<span class="chip">aggregate health: ${d(t.health.status)}</span>
|
|
42
|
-
<span class="chip">components: ${String(t.components.length)}</span>
|
|
43
|
-
<span class="chip">diagnostics: ${String(t.diagnostics.length)}</span>
|
|
44
|
-
<span class="chip">ready/degraded/not-ready: ${e.ready}/${e.degraded}/${e.notReady}</span>
|
|
45
|
-
</div>
|
|
46
|
-
`}function X(t){return t?t.diagnostics.length===0?'<p class="muted">No diagnostics issues.</p>':`<div class="diagnostics-list">
|
|
47
|
-
${t.diagnostics.map(e=>{const n=e.dependsOn&&e.dependsOn.length>0?`<div class="chips">${e.dependsOn.map(l=>`<span class="chip">dependsOn: ${d(l)}</span>`).join("")}</div>`:"";return`<article class="card issue severity-${d(e.severity)}">
|
|
48
|
-
<h3>${d(e.code)}</h3>
|
|
49
|
-
<p><strong>severity:</strong> ${d(e.severity)} · <strong>component:</strong> ${d(e.componentId)}</p>
|
|
50
|
-
<p>${d(e.message)}</p>
|
|
51
|
-
${e.cause?`<p><strong>cause:</strong> ${d(e.cause)}</p>`:""}
|
|
52
|
-
${e.fixHint?`<p><strong>fix hint:</strong> ${d(e.fixHint)}</p>`:""}
|
|
53
|
-
${e.docsUrl?`<p><strong>docs:</strong> <a href="${d(e.docsUrl)}" target="_blank" rel="noreferrer">${d(e.docsUrl)}</a></p>`:""}
|
|
54
|
-
${n}
|
|
55
|
-
</article>`}).join("")}
|
|
56
|
-
</div>`:'<p class="muted">No platform snapshot loaded.</p>'}function y(t){const e=i.filteredSnapshot,n=A(e,i.selectedComponentId),l=e?L(e):"",o=e&&e.components.length>0?H(e,n==null?void 0:n.id):'<p class="muted">No platform components loaded.</p>';R.innerHTML=`
|
|
57
|
-
<main>
|
|
58
|
-
<header>
|
|
59
|
-
<h1>Fluo Studio Platform Snapshot Viewer</h1>
|
|
60
|
-
<p>Load JSON exported by <code>fluo inspect --json</code> (shared platform snapshot/diagnostic schema) and optionally timing JSON from <code>--timing</code>.</p>
|
|
61
|
-
</header>
|
|
62
|
-
|
|
63
|
-
<section class="card uploader" id="drop-zone">
|
|
64
|
-
<h2>Diagnostics file input</h2>
|
|
65
|
-
<p>Drag & drop a JSON file, or choose one manually.</p>
|
|
66
|
-
<input type="file" id="file-input" accept="application/json" />
|
|
67
|
-
<div class="actions">
|
|
68
|
-
<button id="download-json" ${i.rawJson?"":"disabled"}>Download loaded JSON</button>
|
|
69
|
-
<button id="copy-json" ${i.rawJson?"":"disabled"}>Copy loaded JSON</button>
|
|
70
|
-
<button id="copy-mermaid" ${e?"":"disabled"}>Copy Mermaid</button>
|
|
71
|
-
</div>
|
|
72
|
-
${t?`<p class="notice">${d(t)}</p>`:""}
|
|
73
|
-
</section>
|
|
74
|
-
|
|
75
|
-
<section class="card">
|
|
76
|
-
<h2>Snapshot summary</h2>
|
|
77
|
-
${Y(e)}
|
|
78
|
-
</section>
|
|
79
|
-
|
|
80
|
-
<section class="split-grid">
|
|
81
|
-
<div class="card">
|
|
82
|
-
<h2>Search and filtering</h2>
|
|
83
|
-
<label>
|
|
84
|
-
Search component/diagnostic
|
|
85
|
-
<input type="text" id="search" value="${d(i.filter.query)}" placeholder="e.g. redis.default or QUEUE_DEPENDENCY_NOT_READY" />
|
|
86
|
-
</label>
|
|
87
|
-
|
|
88
|
-
<div class="filter-row">
|
|
89
|
-
<span>Component readiness</span>
|
|
90
|
-
${U.map(s=>`<label><input type="checkbox" data-readiness="${s}" ${i.filter.readinessStatuses.includes(s)?"checked":""}/> ${s}</label>`).join("")}
|
|
91
|
-
</div>
|
|
92
|
-
|
|
93
|
-
<div class="filter-row">
|
|
94
|
-
<span>Diagnostic severity</span>
|
|
95
|
-
${P.map(s=>`<label><input type="checkbox" data-severity="${s}" ${i.filter.severities.includes(s)?"checked":""}/> ${s}</label>`).join("")}
|
|
96
|
-
</div>
|
|
97
|
-
</div>
|
|
98
|
-
|
|
99
|
-
<div class="card">
|
|
100
|
-
<h2>Timing</h2>
|
|
101
|
-
${z()}
|
|
102
|
-
</div>
|
|
103
|
-
</section>
|
|
104
|
-
|
|
105
|
-
<section class="card">
|
|
106
|
-
<h2>Platform dependency graph</h2>
|
|
107
|
-
<p class="muted">Component dependencies are rendered directly from the shared platform snapshot schema.</p>
|
|
108
|
-
<div id="graph-host">${o}</div>
|
|
109
|
-
</section>
|
|
110
|
-
|
|
111
|
-
<section class="split-grid">
|
|
112
|
-
<div class="card" id="details-panel">
|
|
113
|
-
<h2>Component details</h2>
|
|
114
|
-
${_(n)}
|
|
115
|
-
</div>
|
|
116
|
-
<div class="card">
|
|
117
|
-
<h2>Mermaid output</h2>
|
|
118
|
-
<pre>${d(l||"No snapshot loaded.")}</pre>
|
|
119
|
-
</div>
|
|
120
|
-
</section>
|
|
121
|
-
|
|
122
|
-
<section class="card">
|
|
123
|
-
<h2>Diagnostics issues</h2>
|
|
124
|
-
<p class="muted">Fix hints and dependency chains are rendered from <code>diagnostics.fixHint</code> and <code>diagnostics.dependsOn</code>.</p>
|
|
125
|
-
${X(e)}
|
|
126
|
-
</section>
|
|
127
|
-
</main>
|
|
128
|
-
`;const r=document.querySelector("#file-input"),a=document.querySelector("#drop-zone"),c=document.querySelector("#search"),p=document.querySelector("#copy-json"),w=document.querySelector("#download-json"),m=document.querySelector("#copy-mermaid"),u=async s=>{const f=await s.text();try{const h=k(f);i.payload=h.payload,i.rawJson=h.rawJson,v(),y("Diagnostics file loaded successfully.")}catch(h){i.payload=void 0,i.filteredSnapshot=void 0,i.selectedComponentId=void 0,i.rawJson=void 0,y(h instanceof Error?h.message:"Failed to parse diagnostics file.")}};r==null||r.addEventListener("change",async s=>{var $;const h=($=s.target.files)==null?void 0:$[0];h&&await u(h)}),a==null||a.addEventListener("dragover",s=>{s.preventDefault(),a.classList.add("drag-active")}),a==null||a.addEventListener("dragleave",()=>{a.classList.remove("drag-active")}),a==null||a.addEventListener("drop",async s=>{var h,$;s.preventDefault(),a.classList.remove("drag-active");const f=($=(h=s.dataTransfer)==null?void 0:h.files)==null?void 0:$[0];f&&await u(f)}),c==null||c.addEventListener("input",s=>{const f=s.target;i.filter.query=f.value,v(),y()}),document.querySelectorAll("input[data-readiness]").forEach(s=>{s.addEventListener("change",()=>{i.filter.readinessStatuses=N(i.filter.readinessStatuses,s.dataset.readiness),v(),y()})}),document.querySelectorAll("input[data-severity]").forEach(s=>{s.addEventListener("change",()=>{i.filter.severities=N(i.filter.severities,s.dataset.severity),v(),y()})}),p==null||p.addEventListener("click",async()=>{if(i.rawJson)try{await O(i.rawJson),y("Loaded JSON copied to clipboard.")}catch(s){y(s instanceof Error?s.message:"Failed to copy JSON.")}}),w==null||w.addEventListener("click",()=>{i.rawJson&&(F("fluo-diagnostics.json",i.rawJson),y("Loaded JSON downloaded."))}),m==null||m.addEventListener("click",async()=>{if(e)try{await O(L(e)),y("Mermaid copied to clipboard.")}catch(s){y(s instanceof Error?s.message:"Failed to copy Mermaid text.")}}),document.querySelectorAll("[data-component]").forEach(s=>{s.addEventListener("click",()=>{const f=s.dataset.component;f&&(i.selectedComponentId=f,y())})})}v();y();
|