@fluojs/studio 1.0.0-beta.2 → 1.0.0-beta.4
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 +24 -7
- package/README.md +24 -7
- package/dist/assets/index-B_CD1DMS.js +128 -0
- package/dist/contracts.d.ts +23 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +47 -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 +3 -2
- package/dist/assets/index-CUoyTA_q.js +0 -128
package/README.ko.md
CHANGED
|
@@ -41,32 +41,40 @@ pnpm add @fluojs/studio
|
|
|
41
41
|
|
|
42
42
|
## 빠른 시작
|
|
43
43
|
|
|
44
|
-
Studio는 fluo CLI에서 내보낸 JSON 파일을 소비합니다. 런타임은 snapshot을 생산하고, CLI는 검사 데이터를 내보내거나 위임하며, Studio는 뷰어와 자동화 호출자가 사용할 수 있도록 snapshot을 파싱, 필터링, 보기, 렌더링하는 공개 헬퍼를 소유합니다.
|
|
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
|
|
48
48
|
fluo inspect ./src/app.module.ts --json > snapshot.json
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
2.
|
|
51
|
+
2. **패키징된 Studio viewer 열기**:
|
|
52
|
+
```bash
|
|
53
|
+
pnpm add -D @fluojs/studio
|
|
54
|
+
node -p "require.resolve('@fluojs/studio/viewer')"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
출력된 `dist/index.html` 경로를 브라우저에서 엽니다. 패키징된 viewer entry는 `@fluojs/studio/viewer`로 export되는 정적 HTML artifact이며, 모노레포 개발 서버를 시작하지 않습니다.
|
|
58
|
+
|
|
59
|
+
저장소 내부 Studio 개발에는 다음 명령을 사용합니다.
|
|
52
60
|
```bash
|
|
53
61
|
pnpm --dir packages/studio dev
|
|
54
62
|
```
|
|
55
63
|
|
|
56
|
-
3. **파일 로드**: Studio 웹 인터페이스에 `snapshot.json` 파일을 드래그 앤 드롭합니다.
|
|
64
|
+
3. **파일 로드**: Studio 웹 인터페이스에 `snapshot.json` 파일을 드래그 앤 드롭합니다. Search와 filter control은 graph, diagnostics, summary가 갱신되는 동안 focus를 유지합니다.
|
|
57
65
|
|
|
58
66
|
## 주요 패턴
|
|
59
67
|
|
|
60
68
|
### 초기화 문제 해결
|
|
61
|
-
**Diagnostics**
|
|
69
|
+
**Diagnostics issues** 섹션을 사용하여 런타임 부트스트랩 과정에서 수집된 이슈들을 확인합니다.
|
|
62
70
|
- 심각도(Error, Warning)별로 필터링합니다.
|
|
63
71
|
- `fixHint`를 통해 문제를 해결하기 위한 구체적인 조치 방법을 확인합니다.
|
|
64
72
|
- `dependsOn`을 통해 어떤 컴포넌트가 실패 지점을 차단하고 있는지 확인합니다.
|
|
65
73
|
|
|
66
74
|
### 아키텍처 다이어그램 내보내기
|
|
67
|
-
1. **
|
|
75
|
+
1. **Platform dependency graph** 섹션을 사용합니다.
|
|
68
76
|
2. 시각화하려는 모듈이나 컴포넌트를 선택합니다.
|
|
69
|
-
3. **
|
|
77
|
+
3. **Copy Mermaid** 버튼을 사용하여 문서에 사용할 수 있는 텍스트 기반 다이어그램을 가져옵니다.
|
|
70
78
|
|
|
71
79
|
자동화에서는 `@fluojs/studio` 또는 `@fluojs/studio/contracts`에서 `renderMermaid(snapshot)`을 호출합니다. 이 헬퍼가 지원되는 snapshot-to-Mermaid 계약입니다. 런타임 패키지는 snapshot producer로 남고, Studio는 그래프 렌더링 시 내부 dependency edge와 외부 dependency node를 처리합니다.
|
|
72
80
|
|
|
@@ -78,7 +86,14 @@ Studio는 주로 웹 애플리케이션이지만, 배포된 패키지는 도구/
|
|
|
78
86
|
|---|---|
|
|
79
87
|
| `PlatformShellSnapshot` | 애플리케이션 상태를 나타내는 핵심 데이터 구조입니다. |
|
|
80
88
|
| `PlatformDiagnosticIssue` | 플랫폼 오류 보고 및 수정을 위한 스키마입니다. |
|
|
81
|
-
| `parseStudioPayload(rawJson)` |
|
|
89
|
+
| `parseStudioPayload(rawJson)` | raw snapshot JSON, standalone timing JSON, snapshot+timing envelope, `fluo inspect --report` artifact를 받아 parsed payload와 원본 JSON string을 반환합니다. |
|
|
90
|
+
| `ParsedPayload` | `parseStudioPayload(...)`가 반환하는 parsed Studio payload shape입니다. |
|
|
91
|
+
| `StudioPayload` | 허용되는 raw snapshot/timing/report input shape입니다. |
|
|
92
|
+
| `StudioReportArtifact` | CI/support 자동화를 위해 summary, snapshot, timing 데이터를 함께 보존한 `fluo inspect --report` artifact입니다. |
|
|
93
|
+
| `StudioReportSummary` | report artifact에 포함되는 summary block입니다. |
|
|
94
|
+
| `FilterState` | `applyFilters(...)`가 받는 filter configuration입니다. |
|
|
95
|
+
| `PlatformReadinessStatus` | component filter에서 사용하는 readiness status union입니다. |
|
|
96
|
+
| `PlatformDiagnosticSeverity` | filter와 issue에서 사용하는 diagnostic severity union입니다. |
|
|
82
97
|
| `applyFilters(snapshot, filter)` | 원본 snapshot을 변경하지 않고 readiness/severity/query 필터를 적용합니다. |
|
|
83
98
|
| `renderMermaid(snapshot)` | 내부 컴포넌트 dependency edge와 외부 dependency node를 포함해 로드된 플랫폼 그래프를 Mermaid 텍스트로 변환합니다. |
|
|
84
99
|
|
|
@@ -88,6 +103,8 @@ Studio는 주로 웹 애플리케이션이지만, 배포된 패키지는 도구/
|
|
|
88
103
|
- `@fluojs/studio/contracts`: 계약 헬퍼를 직접 가져오고 싶은 도구용 명시적 서브패스
|
|
89
104
|
- `@fluojs/studio/viewer`: 브라우저 뷰어 번들의 `dist/index.html` 진입 파일
|
|
90
105
|
|
|
106
|
+
`@fluojs/studio`와 `@fluojs/studio/contracts`는 동등한 helper barrel을 노출합니다.
|
|
107
|
+
|
|
91
108
|
## 관련 패키지
|
|
92
109
|
|
|
93
110
|
- **[@fluojs/cli](../cli/README.ko.md)**: Studio 호환 데이터를 생성하기 위한 `inspect` 명령을 제공합니다.
|
package/README.md
CHANGED
|
@@ -41,32 +41,40 @@ 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. 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.
|
|
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
|
|
48
48
|
fluo inspect ./src/app.module.ts --json > snapshot.json
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
2. **Open Studio**:
|
|
51
|
+
2. **Open the packaged Studio viewer**:
|
|
52
|
+
```bash
|
|
53
|
+
pnpm add -D @fluojs/studio
|
|
54
|
+
node -p "require.resolve('@fluojs/studio/viewer')"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Open the printed `dist/index.html` path in a browser. The packaged viewer entry is a static HTML artifact exported as `@fluojs/studio/viewer`; it does not start the monorepo development server.
|
|
58
|
+
|
|
59
|
+
For repo-local Studio development, use:
|
|
52
60
|
```bash
|
|
53
61
|
pnpm --dir packages/studio dev
|
|
54
62
|
```
|
|
55
63
|
|
|
56
|
-
3. **Load the file**: Drag and drop `snapshot.json` into the Studio web interface.
|
|
64
|
+
3. **Load the file**: Drag and drop `snapshot.json` into the Studio web interface. Search and filter controls preserve focus while the graph, diagnostics, and summary update.
|
|
57
65
|
|
|
58
66
|
## Common Patterns
|
|
59
67
|
|
|
60
68
|
### Troubleshooting Initialization
|
|
61
|
-
Use the **Diagnostics**
|
|
69
|
+
Use the **Diagnostics issues** section to see issues collected during the runtime bootstrap process.
|
|
62
70
|
- Filter by severity (Error, Warning).
|
|
63
71
|
- Use `fixHint` to get actionable advice on how to resolve the issue.
|
|
64
72
|
- View `dependsOn` to see which components are blocking the failing one.
|
|
65
73
|
|
|
66
74
|
### Exporting Architecture Diagrams
|
|
67
|
-
1.
|
|
75
|
+
1. Use the **Platform dependency graph** section.
|
|
68
76
|
2. Select the modules or components you want to visualize.
|
|
69
|
-
3. Use the **
|
|
77
|
+
3. Use the **Copy Mermaid** button to get a text-based diagram for your documentation.
|
|
70
78
|
|
|
71
79
|
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
80
|
|
|
@@ -78,7 +86,14 @@ Studio is primarily a web application, but the published package also exposes th
|
|
|
78
86
|
|---|---|
|
|
79
87
|
| `PlatformShellSnapshot` | The core data structure representing the application state. |
|
|
80
88
|
| `PlatformDiagnosticIssue` | Schema for reporting and fixing platform errors. |
|
|
81
|
-
| `parseStudioPayload(rawJson)` |
|
|
89
|
+
| `parseStudioPayload(rawJson)` | Accepts raw snapshot JSON, standalone timing JSON, snapshot+timing envelopes, and `fluo inspect --report` artifacts; returns the parsed payload plus the original JSON string. |
|
|
90
|
+
| `ParsedPayload` | Parsed Studio payload shape returned by `parseStudioPayload(...)`. |
|
|
91
|
+
| `StudioPayload` | Accepted raw snapshot/timing/report input shapes. |
|
|
92
|
+
| `StudioReportArtifact` | Preserved `fluo inspect --report` artifact with summary, snapshot, and timing data for CI/support automation. |
|
|
93
|
+
| `StudioReportSummary` | Summary block included in report artifacts. |
|
|
94
|
+
| `FilterState` | Filter configuration accepted by `applyFilters(...)`. |
|
|
95
|
+
| `PlatformReadinessStatus` | Readiness status union used by component filters. |
|
|
96
|
+
| `PlatformDiagnosticSeverity` | Diagnostic severity union used by filters and issues. |
|
|
82
97
|
| `applyFilters(snapshot, filter)` | Applies readiness/severity/query filters without mutating the source snapshot. |
|
|
83
98
|
| `renderMermaid(snapshot)` | Produces Mermaid graph text from the loaded platform graph, including internal component dependency edges and external dependency nodes. |
|
|
84
99
|
|
|
@@ -88,6 +103,8 @@ Studio is primarily a web application, but the published package also exposes th
|
|
|
88
103
|
- `@fluojs/studio/contracts`: explicit helper subpath for tooling that wants the contract helpers directly.
|
|
89
104
|
- `@fluojs/studio/viewer`: packaged `dist/index.html` entrypoint for the browser viewer bundle.
|
|
90
105
|
|
|
106
|
+
`@fluojs/studio` and `@fluojs/studio/contracts` expose equivalent helper barrels.
|
|
107
|
+
|
|
91
108
|
## Related Packages
|
|
92
109
|
|
|
93
110
|
- **[@fluojs/cli](../cli/README.md)**: Provides the `inspect` command to generate Studio-compatible exports.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))d(s);new MutationObserver(s=>{for(const o of s)if(o.type==="childList")for(const l of o.addedNodes)l.tagName==="LINK"&&l.rel==="modulepreload"&&d(l)}).observe(document,{childList:!0,subtree:!0});function n(s){const o={};return s.integrity&&(o.integrity=s.integrity),s.referrerPolicy&&(o.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?o.credentials="include":s.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function d(s){if(s.ep)return;s.ep=!0;const o=n(s);fetch(s.href,o)}})();function f(e){return typeof e=="object"&&e!==null}function S(e,t){return Object.hasOwn(e,t)}function A(e){return Array.isArray(e)&&e.every(t=>typeof t=="string")}function L(e){return e==="ready"||e==="not-ready"||e==="degraded"}function O(e){return e==="healthy"||e==="unhealthy"||e==="degraded"}function k(e){return e==="error"||e==="warning"||e==="info"}function D(e){if(!f(e))return null;if(typeof e.generatedAt!="string"||!f(e.readiness)||!f(e.health)||!Array.isArray(e.components)||!Array.isArray(e.diagnostics))throw new Error("Invalid platform snapshot payload.");if(!L(e.readiness.status)||typeof e.readiness.critical!="boolean")throw new Error("Invalid aggregate readiness in platform snapshot payload.");if(!O(e.health.status))throw new Error("Invalid aggregate health in platform snapshot payload.");for(const t of e.components){if(!f(t))throw new Error("Invalid component entry in platform snapshot payload.");if(typeof t.id!="string"||typeof t.kind!="string"||typeof t.state!="string"||!f(t.readiness)||!f(t.health)||!A(t.dependencies)||!f(t.telemetry)||!f(t.ownership)||!f(t.details))throw new Error("Invalid component shape in platform snapshot payload.");if(!L(t.readiness.status)||typeof t.readiness.critical!="boolean")throw new Error("Invalid component readiness in platform snapshot payload.");if(!O(t.health.status))throw new Error("Invalid component health in platform snapshot payload.");if(typeof t.telemetry.namespace!="string"||!f(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(!f(t))throw new Error("Invalid diagnostics issue entry in platform snapshot payload.");if(typeof t.code!="string"||!k(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&&!A(t.dependsOn))throw new Error("Invalid optional diagnostics issue fields in platform snapshot payload.")}return e}function F(e){if(!f(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(!f(t)||typeof t.name!="string"||typeof t.durationMs!="number")throw new Error("Invalid phase entry in bootstrap timing payload.");return e}function J(e){if(!f(e))throw new Error("Invalid inspect report summary payload.");if(typeof e.componentCount!="number"||typeof e.diagnosticCount!="number"||typeof e.errorCount!="number"||!O(e.healthStatus)||!L(e.readinessStatus)||typeof e.timingTotalMs!="number"||typeof e.warningCount!="number")throw new Error("Invalid inspect report summary payload.");return e}function R(e){return e.summary!==void 0||S(e,"snapshot")&&S(e,"timing")&&(S(e,"generatedAt")||S(e,"version"))}function q(e,t,n){if(!f(e)||!R(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:J(e.summary),timing:n,version:1}}function U(e){const t=JSON.parse(e),n=f(t)?t:void 0,d=n!==void 0&&S(n,"snapshot"),s=n!==void 0&&S(n,"timing"),o=n!==void 0&&!d&&!s&&S(n,"version")&&S(n,"totalMs")&&S(n,"phases"),l=D(d?n.snapshot:o?void 0:t),c=F(s?n.timing:l?void 0:t),a=q(t,l,c);if(!l&&!c)throw new Error("Unsupported file format. Expected platform snapshot JSON or timing JSON.");return{payload:{...a?{report:a}:{},...l?{snapshot:l}:{},...c?{timing:c}:{}},rawJson:e}}function P(e,t){const n=t.query.trim().toLowerCase(),d=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(l=>l.toLowerCase().includes(n)):!0),s=e.diagnostics.filter(o=>{var l,c,a,y;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)||(((l=o.cause)==null?void 0:l.toLowerCase().includes(n))??!1)||(((c=o.fixHint)==null?void 0:c.toLowerCase().includes(n))??!1)||(((a=o.docsUrl)==null?void 0:a.toLowerCase().includes(n))??!1)||(((y=o.dependsOn)==null?void 0:y.some(h=>h.toLowerCase().includes(n)))??!1):!0});return{...e,components:d,diagnostics:s}}function x(e){return e.replaceAll('"','\\"')}function H(e){return e.replaceAll(/[^a-zA-Z0-9_]/g,"_")}function _(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 z(e){return`EXT_${H(e)}_${_(e)}`}function C(e){const t=["graph TD"],n=new Map,d=new Map;if(e.components.length===0)return t.push(' EMPTY["No registered platform components"]'),t.join(`
|
|
2
|
+
`);for(const[l,c]of e.components.entries()){const a=`C${String(l+1)}`;n.set(c.id,a),t.push(` ${a}["${x(c.id)}\\nkind: ${x(c.kind)}\\nreadiness: ${c.readiness.status}\\nhealth: ${c.health.status}"]`)}for(const l of e.components){const c=n.get(l.id);if(c)for(const a of l.dependencies){const y=n.get(a);if(y){t.push(` ${c} --> ${y}`);continue}let h=d.get(a);h||(h=z(a),d.set(a,h),t.push(` ${h}["${x(a)}"]`)),t.push(` ${c} --> ${h}`)}}const s=[],o=[];for(const l of e.components){const c=n.get(l.id);c&&(l.readiness.status==="degraded"&&s.push(c),l.readiness.status==="not-ready"&&o.push(c))}return s.length>0&&(t.push(` class ${s.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 M=document.querySelector("#app");if(!M)throw new Error("App root not found.");const Y=M,B=["ready","degraded","not-ready"],X=["error","warning","info"],i={filter:{query:"",readinessStatuses:[],severities:[]}};function p(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}function V(e,t){const n=new Blob([t],{type:"application/json"}),d=URL.createObjectURL(n),s=document.createElement("a");s.href=d,s.download=e,s.click(),URL.revokeObjectURL(d)}async function I(e){if(!navigator.clipboard)throw new Error("Clipboard API is unavailable.");await navigator.clipboard.writeText(e)}function N(e,t){return e.includes(t)?e.filter(n=>n!==t):[...e,t]}function j(e){return e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement}function G(){const e=document.activeElement;if(!(!j(e)||!e.id))return{id:e.id,selectionEnd:e.selectionEnd,selectionStart:e.selectionStart}}function K(e){if(!e)return;const t=document.getElementById(e.id);j(t)&&(t.focus({preventScroll:!0}),e.selectionStart!==null&&e.selectionEnd!==null&&t.setSelectionRange(e.selectionStart,e.selectionEnd))}function T(e,t){return!e||e.components.length===0?void 0:(t?e.components.find(d=>d.id===t):void 0)??e.components[0]}function b(){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=P(e,i.filter);const t=T(i.filteredSnapshot,i.selectedComponentId);i.selectedComponentId=t==null?void 0:t.id}function Q(e,t){const s=Math.min(900,460)/2-70,o=900/2,l=460/2,c=e.components,a=new Map;c.forEach((u,m)=>{const $=Math.PI*2*m/Math.max(c.length,1);a.set(u.id,{x:o+s*Math.cos($),y:l+s*Math.sin($)})});const y=c.flatMap(u=>u.dependencies.map(m=>{const $=a.get(u.id),r=a.get(m);return!$||!r?"":`<line x1="${$.x}" y1="${$.y}" x2="${r.x}" y2="${r.y}" class="edge-line" marker-end="url(#arrow)" />`})).join(""),h=c.map(u=>{const m=a.get(u.id);if(!m)return"";const r=["module-node",u.readiness.status==="not-ready"?"component-not-ready":u.readiness.status==="degraded"?"component-degraded":"component-ready",u.id===t?"module-selected":""].filter(Boolean).join(" ");return`<g>
|
|
4
|
+
<circle cx="${m.x}" cy="${m.y}" r="34" class="${r}" data-component="${p(u.id)}" />
|
|
5
|
+
<text x="${m.x}" y="${m.y+4}" text-anchor="middle" class="module-label">${p(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
|
+
${y}
|
|
13
|
+
${h}
|
|
14
|
+
</svg>`}function W(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: ${p(n)}</span>`).join(""):'<span class="chip">dependsOn: none</span>';return`
|
|
15
|
+
<h3>${p(e.id)}</h3>
|
|
16
|
+
<p class="muted">kind: <strong>${p(e.kind)}</strong> · state: <strong>${p(e.state)}</strong></p>
|
|
17
|
+
<div class="chips">
|
|
18
|
+
<span class="chip">readiness: ${p(e.readiness.status)}</span>
|
|
19
|
+
<span class="chip">critical: ${e.readiness.critical?"true":"false"}</span>
|
|
20
|
+
<span class="chip">health: ${p(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>${p(e.telemetry.namespace)}</code></p>
|
|
25
|
+
<h4>Sanitized details</h4>
|
|
26
|
+
<pre>${p(JSON.stringify(e.details,null,2))}</pre>
|
|
27
|
+
`}function Z(){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>${p(n.name)}</td><td>${n.durationMs.toFixed(3)}</td></tr>`).join("")}
|
|
35
|
+
</tbody>
|
|
36
|
+
</table>
|
|
37
|
+
`:'<p class="muted">Timing not collected.</p>'}function ee(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: ${p(e.generatedAt)}</span>
|
|
40
|
+
<span class="chip">aggregate readiness: ${p(e.readiness.status)}</span>
|
|
41
|
+
<span class="chip">aggregate health: ${p(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 te(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(d=>`<span class="chip">dependsOn: ${p(d)}</span>`).join("")}</div>`:"";return`<article class="card issue severity-${p(t.severity)}">
|
|
48
|
+
<h3>${p(t.code)}</h3>
|
|
49
|
+
<p><strong>severity:</strong> ${p(t.severity)} · <strong>component:</strong> ${p(t.componentId)}</p>
|
|
50
|
+
<p>${p(t.message)}</p>
|
|
51
|
+
${t.cause?`<p><strong>cause:</strong> ${p(t.cause)}</p>`:""}
|
|
52
|
+
${t.fixHint?`<p><strong>fix hint:</strong> ${p(t.fixHint)}</p>`:""}
|
|
53
|
+
${t.docsUrl?`<p><strong>docs:</strong> <a href="${p(t.docsUrl)}" target="_blank" rel="noreferrer">${p(t.docsUrl)}</a></p>`:""}
|
|
54
|
+
${n}
|
|
55
|
+
</article>`}).join("")}
|
|
56
|
+
</div>`:'<p class="muted">No platform snapshot loaded.</p>'}function g(e={}){const t=typeof e=="string"?e:e.message,n=typeof e=="string"||!e.preserveFocus?void 0:G(),d=i.filteredSnapshot,s=T(d,i.selectedComponentId),o=d?C(d):"",l=d&&d.components.length>0?Q(d,s==null?void 0:s.id):'<p class="muted">No platform components loaded.</p>';Y.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" ${d?"":"disabled"}>Copy Mermaid</button>
|
|
71
|
+
</div>
|
|
72
|
+
${t?`<p class="notice">${p(t)}</p>`:""}
|
|
73
|
+
</section>
|
|
74
|
+
|
|
75
|
+
<section class="card">
|
|
76
|
+
<h2>Snapshot summary</h2>
|
|
77
|
+
${ee(d)}
|
|
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="${p(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
|
+
${B.map(r=>`<label><input type="checkbox" id="readiness-${r}" data-readiness="${r}" ${i.filter.readinessStatuses.includes(r)?"checked":""}/> ${r}</label>`).join("")}
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div class="filter-row">
|
|
94
|
+
<span>Diagnostic severity</span>
|
|
95
|
+
${X.map(r=>`<label><input type="checkbox" id="severity-${r}" data-severity="${r}" ${i.filter.severities.includes(r)?"checked":""}/> ${r}</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">${l}</div>
|
|
109
|
+
</section>
|
|
110
|
+
|
|
111
|
+
<section class="split-grid">
|
|
112
|
+
<div class="card" id="details-panel">
|
|
113
|
+
<h2>Component details</h2>
|
|
114
|
+
${W(s)}
|
|
115
|
+
</div>
|
|
116
|
+
<div class="card">
|
|
117
|
+
<h2>Mermaid output</h2>
|
|
118
|
+
<pre>${p(o||"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
|
+
${te(d)}
|
|
126
|
+
</section>
|
|
127
|
+
</main>
|
|
128
|
+
`;const c=document.querySelector("#file-input"),a=document.querySelector("#drop-zone"),y=document.querySelector("#search"),h=document.querySelector("#copy-json"),u=document.querySelector("#download-json"),m=document.querySelector("#copy-mermaid"),$=async r=>{const v=await r.text();try{const w=U(v);i.payload=w.payload,i.rawJson=w.rawJson,b(),g("Diagnostics file loaded successfully.")}catch(w){i.payload=void 0,i.filteredSnapshot=void 0,i.selectedComponentId=void 0,i.rawJson=void 0,g(w instanceof Error?w.message:"Failed to parse diagnostics file.")}};c==null||c.addEventListener("change",async r=>{var E;const w=(E=r.target.files)==null?void 0:E[0];w&&await $(w)}),a==null||a.addEventListener("dragover",r=>{r.preventDefault(),a.classList.add("drag-active")}),a==null||a.addEventListener("dragleave",()=>{a.classList.remove("drag-active")}),a==null||a.addEventListener("drop",async r=>{var w,E;r.preventDefault(),a.classList.remove("drag-active");const v=(E=(w=r.dataTransfer)==null?void 0:w.files)==null?void 0:E[0];v&&await $(v)}),y==null||y.addEventListener("input",r=>{const v=r.target;i.filter.query=v.value,b(),g({preserveFocus:!0})}),document.querySelectorAll("input[data-readiness]").forEach(r=>{r.addEventListener("change",()=>{i.filter.readinessStatuses=N(i.filter.readinessStatuses,r.dataset.readiness),b(),g({preserveFocus:!0})})}),document.querySelectorAll("input[data-severity]").forEach(r=>{r.addEventListener("change",()=>{i.filter.severities=N(i.filter.severities,r.dataset.severity),b(),g({preserveFocus:!0})})}),h==null||h.addEventListener("click",async()=>{if(i.rawJson)try{await I(i.rawJson),g("Loaded JSON copied to clipboard.")}catch(r){g(r instanceof Error?r.message:"Failed to copy JSON.")}}),u==null||u.addEventListener("click",()=>{i.rawJson&&(V("fluo-diagnostics.json",i.rawJson),g("Loaded JSON downloaded."))}),m==null||m.addEventListener("click",async()=>{if(d)try{await I(C(d)),g("Mermaid copied to clipboard.")}catch(r){g(r instanceof Error?r.message:"Failed to copy Mermaid text.")}}),document.querySelectorAll("[data-component]").forEach(r=>{r.addEventListener("click",()=>{const v=r.dataset.component;v&&(i.selectedComponentId=v,g())})}),K(n)}b();g();
|
package/dist/contracts.d.ts
CHANGED
|
@@ -8,10 +8,33 @@ export type PlatformReadinessStatus = PlatformSnapshot['readiness']['status'];
|
|
|
8
8
|
* Diagnostic severities supported by Studio snapshot filtering.
|
|
9
9
|
*/
|
|
10
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
|
+
}
|
|
11
33
|
/**
|
|
12
34
|
* Serializable Studio payload envelope built from inspect snapshot/timing exports.
|
|
13
35
|
*/
|
|
14
36
|
export interface StudioPayload {
|
|
37
|
+
report?: StudioReportArtifact;
|
|
15
38
|
snapshot?: PlatformShellSnapshot;
|
|
16
39
|
timing?: BootstrapTimingDiagnostics;
|
|
17
40
|
}
|
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,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,aAAa;IAC5B,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;
|
|
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
|
@@ -6,6 +6,14 @@
|
|
|
6
6
|
* Diagnostic severities supported by Studio snapshot filtering.
|
|
7
7
|
*/
|
|
8
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
|
+
|
|
9
17
|
/**
|
|
10
18
|
* Serializable Studio payload envelope built from inspect snapshot/timing exports.
|
|
11
19
|
*/
|
|
@@ -21,6 +29,9 @@
|
|
|
21
29
|
function isRecord(value) {
|
|
22
30
|
return typeof value === 'object' && value !== null;
|
|
23
31
|
}
|
|
32
|
+
function hasOwn(value, key) {
|
|
33
|
+
return Object.hasOwn(value, key);
|
|
34
|
+
}
|
|
24
35
|
function isStringArray(value) {
|
|
25
36
|
return Array.isArray(value) && value.every(entry => typeof entry === 'string');
|
|
26
37
|
}
|
|
@@ -93,6 +104,33 @@ function validateTiming(value) {
|
|
|
93
104
|
}
|
|
94
105
|
return value;
|
|
95
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
|
+
}
|
|
96
134
|
|
|
97
135
|
/**
|
|
98
136
|
* Parses a Studio JSON file into the documented snapshot/timing envelope.
|
|
@@ -104,13 +142,20 @@ function validateTiming(value) {
|
|
|
104
142
|
export function parseStudioPayload(rawJson) {
|
|
105
143
|
const parsed = JSON.parse(rawJson);
|
|
106
144
|
const envelope = isRecord(parsed) ? parsed : undefined;
|
|
107
|
-
const
|
|
108
|
-
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);
|
|
109
151
|
if (!snapshot && !timing) {
|
|
110
152
|
throw new Error('Unsupported file format. Expected platform snapshot JSON or timing JSON.');
|
|
111
153
|
}
|
|
112
154
|
return {
|
|
113
155
|
payload: {
|
|
156
|
+
...(report ? {
|
|
157
|
+
report
|
|
158
|
+
} : {}),
|
|
114
159
|
...(snapshot ? {
|
|
115
160
|
snapshot
|
|
116
161
|
} : {}),
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { applyFilters, parseStudioPayload, renderMermaid, type FilterState, type ParsedPayload, type PlatformDiagnosticIssue, type PlatformDiagnosticSeverity, type PlatformReadinessStatus, type PlatformShellSnapshot, 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,uBAAuB,EAC5B,KAAK,0BAA0B,EAC/B,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,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-B_CD1DMS.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.4",
|
|
13
13
|
"private": false,
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"repository": {
|
|
@@ -41,9 +41,10 @@
|
|
|
41
41
|
"dist"
|
|
42
42
|
],
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@fluojs/runtime": "^1.0.0-beta.
|
|
44
|
+
"@fluojs/runtime": "^1.0.0-beta.12"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
+
"happy-dom": "^20.9.0",
|
|
47
48
|
"vitest": "^3.2.4",
|
|
48
49
|
"@fluojs-internal/tooling-vite": "^0.0.0"
|
|
49
50
|
},
|
|
@@ -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();
|