@screenbook/ui 0.0.1
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/.astro/content-assets.mjs +1 -0
- package/.astro/content-modules.mjs +1 -0
- package/.astro/content.d.ts +199 -0
- package/.astro/data-store.json +1 -0
- package/.astro/settings.json +5 -0
- package/.astro/types.d.ts +2 -0
- package/.prettierrc +15 -0
- package/.screenbook/coverage.json +37 -0
- package/.screenbook/screens.json +90 -0
- package/LICENSE +21 -0
- package/astro.config.mjs +18 -0
- package/dist/client/_astro/_baseUniq.BGai4mcc.js +1 -0
- package/dist/client/_astro/arc.DUp0dfXj.js +1 -0
- package/dist/client/_astro/architectureDiagram-VXUJARFQ.De_Gt-YC.js +36 -0
- package/dist/client/_astro/blockDiagram-VD42YOAC.BBt_VNhR.js +122 -0
- package/dist/client/_astro/c4Diagram-YG6GDRKO.DfgUlHvt.js +10 -0
- package/dist/client/_astro/channel.CNyr52v1.js +1 -0
- package/dist/client/_astro/chunk-4BX2VUAB.BL0ar6du.js +1 -0
- package/dist/client/_astro/chunk-55IACEB6.CI6SkZkY.js +1 -0
- package/dist/client/_astro/chunk-B4BG7PRW.Z25N80K6.js +165 -0
- package/dist/client/_astro/chunk-DI55MBZ5.BqjPVmi1.js +220 -0
- package/dist/client/_astro/chunk-FMBD7UC4.bZ9DWnFm.js +15 -0
- package/dist/client/_astro/chunk-QN33PNHL.BkzuUgWB.js +1 -0
- package/dist/client/_astro/chunk-QZHKN3VN.C__d68N_.js +1 -0
- package/dist/client/_astro/chunk-TZMSLE5B.BIpu8bMn.js +1 -0
- package/dist/client/_astro/classDiagram-2ON5EDUG.CxT3aW-h.js +1 -0
- package/dist/client/_astro/classDiagram-v2-WZHVMYZB.CxT3aW-h.js +1 -0
- package/dist/client/_astro/clone.U_lSZ6fe.js +1 -0
- package/dist/client/_astro/cose-bilkent-S5V4N54A.D48yfMll.js +1 -0
- package/dist/client/_astro/coverage.CKIVg4LY.css +1 -0
- package/dist/client/_astro/coverage.DDJMzKCq.css +1 -0
- package/dist/client/_astro/cytoscape.esm.DtBltrT8.js +331 -0
- package/dist/client/_astro/dagre-6UL2VRFP.LKVH7b30.js +4 -0
- package/dist/client/_astro/defaultLocale.C4B-KCzX.js +1 -0
- package/dist/client/_astro/diagram-PSM6KHXK.AHgUjH56.js +24 -0
- package/dist/client/_astro/diagram-QEK2KX5R.DvS33xWZ.js +43 -0
- package/dist/client/_astro/diagram-S2PKOQOG.BWisaYrQ.js +24 -0
- package/dist/client/_astro/erDiagram-Q2GNP2WA.B7oID6oT.js +60 -0
- package/dist/client/_astro/flowDiagram-NV44I4VS.Bb1qJLxr.js +162 -0
- package/dist/client/_astro/ganttDiagram-JELNMOA3.3vGHETyo.js +267 -0
- package/dist/client/_astro/gitGraphDiagram-NY62KEGX.Co2SKcif.js +65 -0
- package/dist/client/_astro/graph.B5fevUwB.js +1 -0
- package/dist/client/_astro/graph.astro_astro_type_script_index_0_lang.1HlATQ1g.js +1 -0
- package/dist/client/_astro/impact.Cvhl64u1.css +1 -0
- package/dist/client/_astro/impact.astro_astro_type_script_index_0_lang.D4cAR9AL.js +6 -0
- package/dist/client/_astro/infoDiagram-WHAUD3N6.B6ULtps1.js +2 -0
- package/dist/client/_astro/init.Gi6I4Gst.js +1 -0
- package/dist/client/_astro/journeyDiagram-XKPGCS4Q.BSOCzWmw.js +139 -0
- package/dist/client/_astro/kanban-definition-3W4ZIXB7.D8KKGc1o.js +89 -0
- package/dist/client/_astro/katex.XbL3y5x-.js +261 -0
- package/dist/client/_astro/layout.8vv24qEg.js +1 -0
- package/dist/client/_astro/linear.B6O9ymuK.js +1 -0
- package/dist/client/_astro/mermaid.core.CReXU7YN.js +256 -0
- package/dist/client/_astro/min.CdGMGVU0.js +1 -0
- package/dist/client/_astro/mindmap-definition-VGOIOE7T.G14HgtDw.js +68 -0
- package/dist/client/_astro/ordinal.BYWQX77i.js +1 -0
- package/dist/client/_astro/pieDiagram-ADFJNKIX.bC2VkyoW.js +30 -0
- package/dist/client/_astro/quadrantDiagram-AYHSOK5B.BlLaQQxO.js +7 -0
- package/dist/client/_astro/requirementDiagram-UZGBJVZJ.DHRnMofO.js +64 -0
- package/dist/client/_astro/sankeyDiagram-TZEHDZUN.BMuaJBmt.js +10 -0
- package/dist/client/_astro/sequenceDiagram-WL72ISMW.CnU62wqy.js +145 -0
- package/dist/client/_astro/stateDiagram-FKZM4ZOC.CewI55YO.js +1 -0
- package/dist/client/_astro/stateDiagram-v2-4FDKWEC3.7xUQqoNr.js +1 -0
- package/dist/client/_astro/timeline-definition-IT6M3QCI.D1PLRwss.js +61 -0
- package/dist/client/_astro/treemap-KMMF4GRG.D3VNVvXF.js +128 -0
- package/dist/client/_astro/xychartDiagram-PRI3JC2R.CQex0-ul.js +7 -0
- package/dist/client/logo.svg +5 -0
- package/dist/server/_@astrojs-ssr-adapter.mjs +1 -0
- package/dist/server/_noop-middleware.mjs +3 -0
- package/dist/server/chunks/_@astrojs-ssr-adapter_DgsYudHz.mjs +4385 -0
- package/dist/server/chunks/astro/server_m7yT4wCr.mjs +2787 -0
- package/dist/server/chunks/astro-designed-error-pages_BvPhMmw0.mjs +364 -0
- package/dist/server/chunks/fs-lite_COtHaKzy.mjs +157 -0
- package/dist/server/chunks/impactAnalysis_Bz5lMdmy.mjs +188 -0
- package/dist/server/chunks/loadScreens_DJf-tycc.mjs +39 -0
- package/dist/server/chunks/node_DoTkMCOi.mjs +1673 -0
- package/dist/server/chunks/remote_B3W5fv4r.mjs +188 -0
- package/dist/server/chunks/sharp_DHNfMLYY.mjs +99 -0
- package/dist/server/entry.mjs +47 -0
- package/dist/server/manifest_-V1HEnR8.mjs +101 -0
- package/dist/server/noop-entrypoint.mjs +3 -0
- package/dist/server/pages/_image.astro.mjs +2 -0
- package/dist/server/pages/coverage.astro.mjs +65 -0
- package/dist/server/pages/graph.astro.mjs +107 -0
- package/dist/server/pages/impact.astro.mjs +52 -0
- package/dist/server/pages/index.astro.mjs +28 -0
- package/dist/server/pages/screen/_id_.astro.mjs +52 -0
- package/dist/server/renderers.mjs +3 -0
- package/package.json +42 -0
- package/public/logo.svg +5 -0
- package/src/layouts/Layout.astro +60 -0
- package/src/pages/coverage.astro +399 -0
- package/src/pages/graph.astro +330 -0
- package/src/pages/impact.astro +459 -0
- package/src/pages/index.astro +167 -0
- package/src/pages/screen/[id].astro +186 -0
- package/src/styles/global.css +862 -0
- package/src/utils/impactAnalysis.ts +297 -0
- package/src/utils/loadCoverage.ts +30 -0
- package/src/utils/loadScreens.ts +18 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import type { Screen } from "@screenbook/core"
|
|
2
|
+
|
|
3
|
+
export interface TransitiveDependency {
|
|
4
|
+
screen: Screen
|
|
5
|
+
path: string[]
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ImpactResult {
|
|
9
|
+
api: string
|
|
10
|
+
direct: Screen[]
|
|
11
|
+
transitive: TransitiveDependency[]
|
|
12
|
+
totalCount: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Check if a screen's dependsOn matches the API name (supports partial matching).
|
|
17
|
+
*/
|
|
18
|
+
function matchesDependency(dependency: string, apiName: string): boolean {
|
|
19
|
+
if (dependency === apiName) {
|
|
20
|
+
return true
|
|
21
|
+
}
|
|
22
|
+
if (dependency.startsWith(`${apiName}.`)) {
|
|
23
|
+
return true
|
|
24
|
+
}
|
|
25
|
+
if (apiName.startsWith(`${dependency}.`)) {
|
|
26
|
+
return true
|
|
27
|
+
}
|
|
28
|
+
return false
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Find screens that directly depend on the given API.
|
|
33
|
+
*/
|
|
34
|
+
function findDirectDependents(screens: Screen[], apiName: string): Screen[] {
|
|
35
|
+
return screens.filter((screen) =>
|
|
36
|
+
screen.dependsOn?.some((dep) => matchesDependency(dep, apiName)),
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build a navigation graph from `next` field.
|
|
42
|
+
*/
|
|
43
|
+
function buildNavigationGraph(screens: Screen[]): Map<string, Set<string>> {
|
|
44
|
+
const graph = new Map<string, Set<string>>()
|
|
45
|
+
|
|
46
|
+
for (const screen of screens) {
|
|
47
|
+
if (!screen.next) continue
|
|
48
|
+
|
|
49
|
+
if (!graph.has(screen.id)) {
|
|
50
|
+
graph.set(screen.id, new Set())
|
|
51
|
+
}
|
|
52
|
+
for (const nextId of screen.next) {
|
|
53
|
+
graph.get(screen.id)!.add(nextId)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return graph
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Find a path from a screen to any directly dependent screen using BFS.
|
|
62
|
+
*/
|
|
63
|
+
function findPathToDirectDependent(
|
|
64
|
+
startId: string,
|
|
65
|
+
targetIds: Set<string>,
|
|
66
|
+
graph: Map<string, Set<string>>,
|
|
67
|
+
maxDepth: number,
|
|
68
|
+
): string[] | null {
|
|
69
|
+
const queue: Array<{ id: string; path: string[] }> = [
|
|
70
|
+
{ id: startId, path: [startId] },
|
|
71
|
+
]
|
|
72
|
+
const localVisited = new Set<string>([startId])
|
|
73
|
+
|
|
74
|
+
while (queue.length > 0) {
|
|
75
|
+
const current = queue.shift()!
|
|
76
|
+
|
|
77
|
+
if (current.path.length > maxDepth + 1) {
|
|
78
|
+
continue
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const neighbors = graph.get(current.id)
|
|
82
|
+
if (!neighbors) {
|
|
83
|
+
continue
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const neighborId of neighbors) {
|
|
87
|
+
if (localVisited.has(neighborId)) {
|
|
88
|
+
continue
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const newPath = [...current.path, neighborId]
|
|
92
|
+
|
|
93
|
+
if (newPath.length > maxDepth + 1) {
|
|
94
|
+
continue
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (targetIds.has(neighborId)) {
|
|
98
|
+
return newPath
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
localVisited.add(neighborId)
|
|
102
|
+
queue.push({ id: neighborId, path: newPath })
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return null
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Find all transitive dependents using BFS.
|
|
111
|
+
*/
|
|
112
|
+
function findTransitiveDependents(
|
|
113
|
+
screens: Screen[],
|
|
114
|
+
directDependentIds: Set<string>,
|
|
115
|
+
maxDepth: number,
|
|
116
|
+
): TransitiveDependency[] {
|
|
117
|
+
const navigationGraph = buildNavigationGraph(screens)
|
|
118
|
+
const transitive: TransitiveDependency[] = []
|
|
119
|
+
const visited = new Set<string>()
|
|
120
|
+
|
|
121
|
+
for (const screen of screens) {
|
|
122
|
+
if (directDependentIds.has(screen.id)) {
|
|
123
|
+
continue
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const path = findPathToDirectDependent(
|
|
127
|
+
screen.id,
|
|
128
|
+
directDependentIds,
|
|
129
|
+
navigationGraph,
|
|
130
|
+
maxDepth,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
if (path && !visited.has(screen.id)) {
|
|
134
|
+
visited.add(screen.id)
|
|
135
|
+
transitive.push({
|
|
136
|
+
screen,
|
|
137
|
+
path,
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return transitive
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Analyze the impact of a change to an API on the screen catalog.
|
|
147
|
+
*/
|
|
148
|
+
export function analyzeImpact(
|
|
149
|
+
screens: Screen[],
|
|
150
|
+
apiName: string,
|
|
151
|
+
maxDepth = 3,
|
|
152
|
+
): ImpactResult {
|
|
153
|
+
const direct = findDirectDependents(screens, apiName)
|
|
154
|
+
const directIds = new Set(direct.map((s) => s.id))
|
|
155
|
+
const transitive = findTransitiveDependents(screens, directIds, maxDepth)
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
api: apiName,
|
|
159
|
+
direct,
|
|
160
|
+
transitive,
|
|
161
|
+
totalCount: direct.length + transitive.length,
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get all unique APIs from screens' dependsOn fields.
|
|
167
|
+
*/
|
|
168
|
+
export function getAllApis(screens: Screen[]): string[] {
|
|
169
|
+
const apis = new Set<string>()
|
|
170
|
+
for (const screen of screens) {
|
|
171
|
+
if (screen.dependsOn) {
|
|
172
|
+
for (const dep of screen.dependsOn) {
|
|
173
|
+
apis.add(dep)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return Array.from(apis).sort()
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Count how many screens depend on each API.
|
|
182
|
+
*/
|
|
183
|
+
export function getApiDependencyCount(screens: Screen[]): Map<string, number> {
|
|
184
|
+
const counts = new Map<string, number>()
|
|
185
|
+
for (const screen of screens) {
|
|
186
|
+
if (screen.dependsOn) {
|
|
187
|
+
for (const dep of screen.dependsOn) {
|
|
188
|
+
counts.set(dep, (counts.get(dep) || 0) + 1)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return counts
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Generate Mermaid graph with impact highlighting.
|
|
197
|
+
*/
|
|
198
|
+
export function generateImpactMermaid(
|
|
199
|
+
screens: Screen[],
|
|
200
|
+
result: ImpactResult,
|
|
201
|
+
): string {
|
|
202
|
+
const directIds = new Set(result.direct.map((s) => s.id))
|
|
203
|
+
const transitiveIds = new Set(result.transitive.map((t) => t.screen.id))
|
|
204
|
+
|
|
205
|
+
const lines: string[] = ["flowchart TD"]
|
|
206
|
+
|
|
207
|
+
// Define styles - high contrast colors with readable text
|
|
208
|
+
lines.push(" classDef direct fill:#dc2626,stroke:#fef2f2,color:#ffffff,stroke-width:3px,font-weight:bold")
|
|
209
|
+
lines.push(" classDef transitive fill:#ea580c,stroke:#fff7ed,color:#ffffff,stroke-width:3px,font-weight:bold")
|
|
210
|
+
lines.push(" classDef normal fill:#1e293b,stroke:#64748b,color:#e2e8f0,stroke-width:1px")
|
|
211
|
+
|
|
212
|
+
// Add nodes
|
|
213
|
+
for (const screen of screens) {
|
|
214
|
+
const label = screen.title.replace(/"/g, "'")
|
|
215
|
+
const id = screen.id.replace(/\./g, "_")
|
|
216
|
+
lines.push(` ${id}["${label}"]`)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
lines.push("")
|
|
220
|
+
|
|
221
|
+
// Add edges
|
|
222
|
+
for (const screen of screens) {
|
|
223
|
+
if (screen.next) {
|
|
224
|
+
const fromId = screen.id.replace(/\./g, "_")
|
|
225
|
+
for (const nextId of screen.next) {
|
|
226
|
+
const toId = nextId.replace(/\./g, "_")
|
|
227
|
+
lines.push(` ${fromId} --> ${toId}`)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
lines.push("")
|
|
233
|
+
|
|
234
|
+
// Apply styles
|
|
235
|
+
const directNodes = screens
|
|
236
|
+
.filter((s) => directIds.has(s.id))
|
|
237
|
+
.map((s) => s.id.replace(/\./g, "_"))
|
|
238
|
+
const transitiveNodes = screens
|
|
239
|
+
.filter((s) => transitiveIds.has(s.id))
|
|
240
|
+
.map((s) => s.id.replace(/\./g, "_"))
|
|
241
|
+
const normalNodes = screens
|
|
242
|
+
.filter((s) => !directIds.has(s.id) && !transitiveIds.has(s.id))
|
|
243
|
+
.map((s) => s.id.replace(/\./g, "_"))
|
|
244
|
+
|
|
245
|
+
if (directNodes.length > 0) {
|
|
246
|
+
lines.push(` class ${directNodes.join(",")} direct`)
|
|
247
|
+
}
|
|
248
|
+
if (transitiveNodes.length > 0) {
|
|
249
|
+
lines.push(` class ${transitiveNodes.join(",")} transitive`)
|
|
250
|
+
}
|
|
251
|
+
if (normalNodes.length > 0) {
|
|
252
|
+
lines.push(` class ${normalNodes.join(",")} normal`)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return lines.join("\n")
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Format the impact result as Markdown for PR comments.
|
|
260
|
+
*/
|
|
261
|
+
export function formatImpactMarkdown(result: ImpactResult): string {
|
|
262
|
+
const lines: string[] = []
|
|
263
|
+
|
|
264
|
+
lines.push(`## Impact Analysis: \`${result.api}\``)
|
|
265
|
+
lines.push("")
|
|
266
|
+
|
|
267
|
+
if (result.totalCount === 0) {
|
|
268
|
+
lines.push("No screens depend on this API.")
|
|
269
|
+
return lines.join("\n")
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
lines.push(
|
|
273
|
+
`**${result.totalCount} screen${result.totalCount > 1 ? "s" : ""} affected**`,
|
|
274
|
+
)
|
|
275
|
+
lines.push("")
|
|
276
|
+
|
|
277
|
+
if (result.direct.length > 0) {
|
|
278
|
+
lines.push(`### Direct Dependencies (${result.direct.length})`)
|
|
279
|
+
lines.push("")
|
|
280
|
+
for (const screen of result.direct) {
|
|
281
|
+
const owner = screen.owner?.length ? ` - ${screen.owner.join(", ")}` : ""
|
|
282
|
+
lines.push(`- **${screen.title}** (\`${screen.route}\`)${owner}`)
|
|
283
|
+
}
|
|
284
|
+
lines.push("")
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (result.transitive.length > 0) {
|
|
288
|
+
lines.push(`### Transitive Dependencies (${result.transitive.length})`)
|
|
289
|
+
lines.push("")
|
|
290
|
+
for (const { screen, path } of result.transitive) {
|
|
291
|
+
lines.push(`- **${screen.title}** via \`${path.join(" → ")}\``)
|
|
292
|
+
}
|
|
293
|
+
lines.push("")
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return lines.join("\n")
|
|
297
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
2
|
+
import { join } from "node:path"
|
|
3
|
+
|
|
4
|
+
export interface CoverageData {
|
|
5
|
+
total: number
|
|
6
|
+
covered: number
|
|
7
|
+
percentage: number
|
|
8
|
+
missing: Array<{
|
|
9
|
+
route: string
|
|
10
|
+
suggestedPath: string
|
|
11
|
+
}>
|
|
12
|
+
byOwner: Record<string, { count: number; screens: string[] }>
|
|
13
|
+
byTag: Record<string, number>
|
|
14
|
+
timestamp: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function loadCoverage(): CoverageData | null {
|
|
18
|
+
const coveragePath = join(process.cwd(), ".screenbook", "coverage.json")
|
|
19
|
+
|
|
20
|
+
if (!existsSync(coveragePath)) {
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const content = readFileSync(coveragePath, "utf-8")
|
|
26
|
+
return JSON.parse(content) as CoverageData
|
|
27
|
+
} catch {
|
|
28
|
+
return null
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
2
|
+
import { join } from "node:path"
|
|
3
|
+
import type { Screen } from "@screenbook/core"
|
|
4
|
+
|
|
5
|
+
export function loadScreens(): Screen[] {
|
|
6
|
+
const screensPath = join(process.cwd(), ".screenbook", "screens.json")
|
|
7
|
+
|
|
8
|
+
if (!existsSync(screensPath)) {
|
|
9
|
+
return []
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const content = readFileSync(screensPath, "utf-8")
|
|
14
|
+
return JSON.parse(content) as Screen[]
|
|
15
|
+
} catch {
|
|
16
|
+
return []
|
|
17
|
+
}
|
|
18
|
+
}
|