@principal-ai/principal-view-core 0.25.0 → 0.26.0
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/dist/discovery/CanvasFileManifest.d.ts +86 -0
- package/dist/discovery/CanvasFileManifest.d.ts.map +1 -0
- package/dist/discovery/CanvasFileManifest.js +338 -0
- package/dist/discovery/CanvasFileManifest.js.map +1 -0
- package/dist/discovery/types.d.ts +131 -0
- package/dist/discovery/types.d.ts.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/storyboard/builder.d.ts +3 -14
- package/dist/storyboard/builder.d.ts.map +1 -1
- package/dist/storyboard/builder.js +6 -33
- package/dist/storyboard/builder.js.map +1 -1
- package/dist/storyboard/index.d.ts +1 -1
- package/dist/storyboard/index.d.ts.map +1 -1
- package/dist/storyboard/index.js +1 -2
- package/dist/storyboard/index.js.map +1 -1
- package/dist/storyboard/types.d.ts +8 -9
- package/dist/storyboard/types.d.ts.map +1 -1
- package/dist/storyboard/types.js +1 -1
- package/package.json +1 -1
- package/src/discovery/CanvasFileManifest.test.ts +521 -0
- package/src/discovery/CanvasFileManifest.ts +419 -0
- package/src/discovery/types.ts +176 -0
- package/src/index.ts +20 -1
- package/src/storyboard/builder.ts +9 -35
- package/src/storyboard/index.ts +0 -1
- package/src/storyboard/types.ts +9 -9
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canvas File Manifest Builder
|
|
3
|
+
*
|
|
4
|
+
* Utilities for building aggregated file manifests from canvases, workflows, and storyboards.
|
|
5
|
+
* These manifests provide structured access to all files associated with OTEL canvases,
|
|
6
|
+
* with bidirectional mappings between files, nodes, and events.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
ExtendedCanvas,
|
|
11
|
+
ExtendedCanvasNode,
|
|
12
|
+
OtelNode,
|
|
13
|
+
OtelEventNode,
|
|
14
|
+
} from '../types/canvas';
|
|
15
|
+
import type { WorkflowTemplate, WorkflowScenario } from '../workflow/types';
|
|
16
|
+
import type {
|
|
17
|
+
CanvasFileManifest,
|
|
18
|
+
WorkflowFileManifest,
|
|
19
|
+
StoryboardFileManifest,
|
|
20
|
+
CanvasFile,
|
|
21
|
+
CanvasFileRole,
|
|
22
|
+
CanvasFileOrigin,
|
|
23
|
+
CanvasType,
|
|
24
|
+
} from './types';
|
|
25
|
+
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// Type Guards
|
|
28
|
+
// =============================================================================
|
|
29
|
+
|
|
30
|
+
function isOtelNode(node: ExtendedCanvasNode): node is OtelNode {
|
|
31
|
+
return node.type.startsWith('otel-');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isOtelEventNode(node: ExtendedCanvasNode): node is OtelEventNode {
|
|
35
|
+
return node.type === 'otel-event';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// =============================================================================
|
|
39
|
+
// Node File Extraction
|
|
40
|
+
// =============================================================================
|
|
41
|
+
|
|
42
|
+
interface ExtractedFile {
|
|
43
|
+
path: string;
|
|
44
|
+
role: CanvasFileRole;
|
|
45
|
+
origin: CanvasFileOrigin;
|
|
46
|
+
eventName?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function extractNodeFiles(node: ExtendedCanvasNode): ExtractedFile[] {
|
|
50
|
+
const files: ExtractedFile[] = [];
|
|
51
|
+
|
|
52
|
+
// Only process OTEL nodes
|
|
53
|
+
if (!isOtelNode(node) || !node.otel) {
|
|
54
|
+
return files;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Get event name if this is an otel-event node
|
|
58
|
+
let eventName: string | undefined;
|
|
59
|
+
if (isOtelEventNode(node)) {
|
|
60
|
+
eventName = node.event?.name ?? node.eventRef;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const origin: CanvasFileOrigin = node.otel.origin ?? 'internal';
|
|
64
|
+
|
|
65
|
+
// Instrumentation files
|
|
66
|
+
if (node.otel.files) {
|
|
67
|
+
for (const path of node.otel.files) {
|
|
68
|
+
files.push({ path, role: 'instrumentation', origin, eventName });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// References
|
|
73
|
+
if (node.otel.references) {
|
|
74
|
+
for (const path of node.otel.references) {
|
|
75
|
+
files.push({ path, role: 'reference', origin: 'external', eventName });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return files;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// =============================================================================
|
|
83
|
+
// Canvas Manifest Builder
|
|
84
|
+
// =============================================================================
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Build a file manifest from a canvas
|
|
88
|
+
*
|
|
89
|
+
* @param canvas - The parsed ExtendedCanvas
|
|
90
|
+
* @param canvasId - Canvas identifier
|
|
91
|
+
* @param canvasPath - Canvas file path
|
|
92
|
+
* @param canvasType - Canvas type (otel, scopes, spans, resources)
|
|
93
|
+
* @returns Aggregated file manifest
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```typescript
|
|
97
|
+
* const manifest = buildCanvasFileManifest(
|
|
98
|
+
* canvas,
|
|
99
|
+
* 'my-canvas',
|
|
100
|
+
* '.principal-views/my-canvas.otel.canvas',
|
|
101
|
+
* 'otel'
|
|
102
|
+
* );
|
|
103
|
+
*
|
|
104
|
+
* // Get all instrumentation files
|
|
105
|
+
* const instrumentationFiles = manifest.byRole.instrumentation;
|
|
106
|
+
*
|
|
107
|
+
* // Find which nodes reference a specific file
|
|
108
|
+
* const nodeIds = manifest.fileToNodes.get('src/api/checkout.ts');
|
|
109
|
+
*
|
|
110
|
+
* // Get all files for a specific node
|
|
111
|
+
* const files = manifest.nodeToFiles.get('node-123');
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
export function buildCanvasFileManifest(
|
|
115
|
+
canvas: ExtendedCanvas,
|
|
116
|
+
canvasId: string,
|
|
117
|
+
canvasPath: string,
|
|
118
|
+
canvasType: CanvasType = 'otel'
|
|
119
|
+
): CanvasFileManifest {
|
|
120
|
+
const fileMap = new Map<string, CanvasFile>();
|
|
121
|
+
const fileToNodes = new Map<string, string[]>();
|
|
122
|
+
const nodeToFiles = new Map<string, string[]>();
|
|
123
|
+
const eventToFiles = new Map<string, string[]>();
|
|
124
|
+
const eventNames = new Set<string>();
|
|
125
|
+
|
|
126
|
+
let nodesWithFiles = 0;
|
|
127
|
+
let nodesWithoutFiles = 0;
|
|
128
|
+
|
|
129
|
+
for (const node of canvas.nodes ?? []) {
|
|
130
|
+
const nodeFiles = extractNodeFiles(node);
|
|
131
|
+
|
|
132
|
+
if (nodeFiles.length > 0) {
|
|
133
|
+
nodesWithFiles++;
|
|
134
|
+
nodeToFiles.set(
|
|
135
|
+
node.id,
|
|
136
|
+
nodeFiles.map((f) => f.path)
|
|
137
|
+
);
|
|
138
|
+
} else {
|
|
139
|
+
nodesWithoutFiles++;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
for (const { path, role, origin, eventName } of nodeFiles) {
|
|
143
|
+
// Track event names
|
|
144
|
+
if (eventName) {
|
|
145
|
+
eventNames.add(eventName);
|
|
146
|
+
const existingEventFiles = eventToFiles.get(eventName) ?? [];
|
|
147
|
+
if (!existingEventFiles.includes(path)) {
|
|
148
|
+
existingEventFiles.push(path);
|
|
149
|
+
eventToFiles.set(eventName, existingEventFiles);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Update fileToNodes mapping
|
|
154
|
+
const existingNodes = fileToNodes.get(path) ?? [];
|
|
155
|
+
if (!existingNodes.includes(node.id)) {
|
|
156
|
+
existingNodes.push(node.id);
|
|
157
|
+
fileToNodes.set(path, existingNodes);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Update or create CanvasFile entry
|
|
161
|
+
const existing = fileMap.get(path);
|
|
162
|
+
if (existing) {
|
|
163
|
+
if (!existing.nodeIds.includes(node.id)) {
|
|
164
|
+
existing.nodeIds.push(node.id);
|
|
165
|
+
}
|
|
166
|
+
if (!existing.nodeTypes.includes(node.type)) {
|
|
167
|
+
existing.nodeTypes.push(node.type);
|
|
168
|
+
}
|
|
169
|
+
if (eventName && !existing.eventNames.includes(eventName)) {
|
|
170
|
+
existing.eventNames.push(eventName);
|
|
171
|
+
}
|
|
172
|
+
// Role priority: instrumentation > root-span > reference
|
|
173
|
+
const rolePriority: Record<CanvasFileRole, number> = {
|
|
174
|
+
instrumentation: 3,
|
|
175
|
+
'root-span': 2,
|
|
176
|
+
reference: 1,
|
|
177
|
+
};
|
|
178
|
+
if (rolePriority[role] > rolePriority[existing.role]) {
|
|
179
|
+
existing.role = role;
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
fileMap.set(path, {
|
|
183
|
+
path,
|
|
184
|
+
role,
|
|
185
|
+
origin,
|
|
186
|
+
level: 'canvas-node',
|
|
187
|
+
nodeIds: [node.id],
|
|
188
|
+
nodeTypes: [node.type],
|
|
189
|
+
workflowIds: [],
|
|
190
|
+
eventNames: eventName ? [eventName] : [],
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const files = Array.from(fileMap.values());
|
|
197
|
+
|
|
198
|
+
// Build byRole lookup
|
|
199
|
+
const byRole: Record<CanvasFileRole, CanvasFile[]> = {
|
|
200
|
+
instrumentation: files.filter((f) => f.role === 'instrumentation'),
|
|
201
|
+
reference: files.filter((f) => f.role === 'reference'),
|
|
202
|
+
'root-span': files.filter((f) => f.role === 'root-span'),
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// Build byOrigin lookup
|
|
206
|
+
const byOrigin: Record<CanvasFileOrigin, CanvasFile[]> = {
|
|
207
|
+
internal: files.filter((f) => f.origin === 'internal'),
|
|
208
|
+
external: files.filter((f) => f.origin === 'external'),
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
canvasId,
|
|
213
|
+
canvasPath,
|
|
214
|
+
canvasType,
|
|
215
|
+
files,
|
|
216
|
+
byRole,
|
|
217
|
+
byOrigin,
|
|
218
|
+
fileToNodes,
|
|
219
|
+
nodeToFiles,
|
|
220
|
+
eventToFiles,
|
|
221
|
+
stats: {
|
|
222
|
+
totalFiles: files.length,
|
|
223
|
+
internalFiles: byOrigin.internal.length,
|
|
224
|
+
externalFiles: byOrigin.external.length,
|
|
225
|
+
instrumentationFiles: byRole.instrumentation.length,
|
|
226
|
+
referenceFiles: byRole.reference.length,
|
|
227
|
+
nodesWithFiles,
|
|
228
|
+
nodesWithoutFiles,
|
|
229
|
+
uniqueEventNames: eventNames.size,
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// =============================================================================
|
|
235
|
+
// Workflow Manifest Builder
|
|
236
|
+
// =============================================================================
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Get files relevant to a specific scenario
|
|
240
|
+
*/
|
|
241
|
+
function getFilesForScenario(
|
|
242
|
+
scenario: WorkflowScenario,
|
|
243
|
+
canvasManifest: CanvasFileManifest,
|
|
244
|
+
workflowFiles: CanvasFile[]
|
|
245
|
+
): CanvasFile[] {
|
|
246
|
+
const files: CanvasFile[] = [];
|
|
247
|
+
|
|
248
|
+
// Always include workflow-level files (root span)
|
|
249
|
+
files.push(...workflowFiles);
|
|
250
|
+
|
|
251
|
+
// Get event names from scenario template
|
|
252
|
+
const eventNames = Object.keys(scenario.template.events ?? {});
|
|
253
|
+
|
|
254
|
+
// Find files for each event
|
|
255
|
+
for (const eventName of eventNames) {
|
|
256
|
+
const eventFiles = canvasManifest.eventToFiles.get(eventName) ?? [];
|
|
257
|
+
for (const filePath of eventFiles) {
|
|
258
|
+
const canvasFile = canvasManifest.files.find((f) => f.path === filePath);
|
|
259
|
+
if (canvasFile && !files.some((f) => f.path === filePath)) {
|
|
260
|
+
files.push(canvasFile);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return files;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Build a file manifest for a workflow (includes canvas files)
|
|
270
|
+
*
|
|
271
|
+
* @param canvasManifest - The canvas manifest to extend
|
|
272
|
+
* @param workflow - The workflow template
|
|
273
|
+
* @param workflowId - Workflow identifier
|
|
274
|
+
* @param workflowPath - Workflow file path
|
|
275
|
+
* @returns Workflow file manifest with scenario filtering
|
|
276
|
+
*
|
|
277
|
+
* @example
|
|
278
|
+
* ```typescript
|
|
279
|
+
* const workflowManifest = buildWorkflowFileManifest(
|
|
280
|
+
* canvasManifest,
|
|
281
|
+
* workflow,
|
|
282
|
+
* 'checkout-workflow',
|
|
283
|
+
* '.principal-views/checkout/checkout.workflow.json'
|
|
284
|
+
* );
|
|
285
|
+
*
|
|
286
|
+
* // Get files for a specific scenario
|
|
287
|
+
* const scenarioFiles = workflowManifest.byScenario.get('success-scenario');
|
|
288
|
+
* ```
|
|
289
|
+
*/
|
|
290
|
+
export function buildWorkflowFileManifest(
|
|
291
|
+
canvasManifest: CanvasFileManifest,
|
|
292
|
+
workflow: WorkflowTemplate,
|
|
293
|
+
workflowId: string,
|
|
294
|
+
workflowPath: string
|
|
295
|
+
): WorkflowFileManifest {
|
|
296
|
+
// Extract workflow-level files
|
|
297
|
+
const workflowFiles: CanvasFile[] = (workflow.files ?? []).map((path) => ({
|
|
298
|
+
path,
|
|
299
|
+
role: 'root-span' as CanvasFileRole,
|
|
300
|
+
origin: 'internal' as CanvasFileOrigin,
|
|
301
|
+
level: 'workflow' as const,
|
|
302
|
+
nodeIds: [],
|
|
303
|
+
nodeTypes: [],
|
|
304
|
+
workflowIds: [workflowId],
|
|
305
|
+
eventNames: [],
|
|
306
|
+
}));
|
|
307
|
+
|
|
308
|
+
// Merge canvas files with workflow files
|
|
309
|
+
const allFilesMap = new Map<string, CanvasFile>();
|
|
310
|
+
|
|
311
|
+
// Add canvas files first
|
|
312
|
+
for (const file of canvasManifest.files) {
|
|
313
|
+
allFilesMap.set(file.path, { ...file });
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Add/merge workflow files
|
|
317
|
+
for (const file of workflowFiles) {
|
|
318
|
+
const existing = allFilesMap.get(file.path);
|
|
319
|
+
if (existing) {
|
|
320
|
+
existing.workflowIds.push(workflowId);
|
|
321
|
+
// Upgrade role if workflow-level
|
|
322
|
+
if (file.role === 'root-span') {
|
|
323
|
+
existing.role = 'root-span';
|
|
324
|
+
existing.level = 'workflow';
|
|
325
|
+
}
|
|
326
|
+
} else {
|
|
327
|
+
allFilesMap.set(file.path, file);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const allFiles = Array.from(allFilesMap.values());
|
|
332
|
+
|
|
333
|
+
// Build scenario -> files mapping
|
|
334
|
+
const byScenario = new Map<string, CanvasFile[]>();
|
|
335
|
+
for (const scenario of workflow.scenarios) {
|
|
336
|
+
const scenarioFiles = getFilesForScenario(scenario, canvasManifest, workflowFiles);
|
|
337
|
+
byScenario.set(scenario.id, scenarioFiles);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
...canvasManifest,
|
|
342
|
+
workflowId,
|
|
343
|
+
workflowPath,
|
|
344
|
+
rootSpan: workflow.rootSpan ?? workflow.spanPattern,
|
|
345
|
+
workflowFiles,
|
|
346
|
+
allFiles,
|
|
347
|
+
byScenario,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// =============================================================================
|
|
352
|
+
// Storyboard Manifest Builder
|
|
353
|
+
// =============================================================================
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Build a file manifest for an entire storyboard
|
|
357
|
+
*
|
|
358
|
+
* @param canvasManifest - The main canvas manifest
|
|
359
|
+
* @param workflowManifests - Array of workflow manifests
|
|
360
|
+
* @param storyboardId - Storyboard identifier
|
|
361
|
+
* @param storyboardPath - Storyboard folder path
|
|
362
|
+
* @returns Storyboard file manifest aggregating all files
|
|
363
|
+
*
|
|
364
|
+
* @example
|
|
365
|
+
* ```typescript
|
|
366
|
+
* const storyboardManifest = buildStoryboardFileManifest(
|
|
367
|
+
* canvasManifest,
|
|
368
|
+
* workflowManifests,
|
|
369
|
+
* 'checkout-flow',
|
|
370
|
+
* '.principal-views/checkout-flow'
|
|
371
|
+
* );
|
|
372
|
+
*
|
|
373
|
+
* // Get all files across canvas and workflows
|
|
374
|
+
* const allFiles = storyboardManifest.allFiles;
|
|
375
|
+
* ```
|
|
376
|
+
*/
|
|
377
|
+
export function buildStoryboardFileManifest(
|
|
378
|
+
canvasManifest: CanvasFileManifest,
|
|
379
|
+
workflowManifests: WorkflowFileManifest[],
|
|
380
|
+
storyboardId: string,
|
|
381
|
+
storyboardPath: string
|
|
382
|
+
): StoryboardFileManifest {
|
|
383
|
+
// Merge all files across canvas and workflows
|
|
384
|
+
const allFilesMap = new Map<string, CanvasFile>();
|
|
385
|
+
|
|
386
|
+
for (const file of canvasManifest.files) {
|
|
387
|
+
allFilesMap.set(file.path, { ...file });
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
for (const workflow of workflowManifests) {
|
|
391
|
+
for (const file of workflow.workflowFiles) {
|
|
392
|
+
const existing = allFilesMap.get(file.path);
|
|
393
|
+
if (existing) {
|
|
394
|
+
existing.workflowIds.push(...file.workflowIds);
|
|
395
|
+
} else {
|
|
396
|
+
allFilesMap.set(file.path, { ...file });
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const allFiles = Array.from(allFilesMap.values());
|
|
402
|
+
|
|
403
|
+
// Count scenarios across all workflows
|
|
404
|
+
const scenarioCount = workflowManifests.reduce((sum, w) => sum + w.byScenario.size, 0);
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
storyboardId,
|
|
408
|
+
storyboardPath,
|
|
409
|
+
canvas: canvasManifest,
|
|
410
|
+
workflows: workflowManifests,
|
|
411
|
+
allFiles,
|
|
412
|
+
stats: {
|
|
413
|
+
...canvasManifest.stats,
|
|
414
|
+
workflowCount: workflowManifests.length,
|
|
415
|
+
scenarioCount,
|
|
416
|
+
rootSpanFiles: allFiles.filter((f) => f.role === 'root-span').length,
|
|
417
|
+
},
|
|
418
|
+
};
|
|
419
|
+
}
|
package/src/discovery/types.ts
CHANGED
|
@@ -208,3 +208,179 @@ export interface CanvasDiscoveryResultWithContent {
|
|
|
208
208
|
errors: Array<{ path: string; error: string }>;
|
|
209
209
|
warnings: Array<{ path: string; message: string; type: 'deprecation' }>;
|
|
210
210
|
}
|
|
211
|
+
|
|
212
|
+
// =============================================================================
|
|
213
|
+
// Canvas File Manifest Types
|
|
214
|
+
// =============================================================================
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Classification of how a file relates to a canvas/workflow
|
|
218
|
+
*/
|
|
219
|
+
export type CanvasFileRole =
|
|
220
|
+
| 'instrumentation' // File contains OTEL instrumentation (from otel.files)
|
|
221
|
+
| 'reference' // External reference/documentation (from otel.references)
|
|
222
|
+
| 'root-span'; // Root span instrumentation (from workflow.files)
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Origin of the file - internal to repo or external
|
|
226
|
+
*/
|
|
227
|
+
export type CanvasFileOrigin = 'internal' | 'external';
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Source level where the file was defined
|
|
231
|
+
*/
|
|
232
|
+
export type CanvasFileLevel = 'canvas-node' | 'workflow';
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* A file referenced by a canvas, workflow, or scenario
|
|
236
|
+
*/
|
|
237
|
+
export interface CanvasFile {
|
|
238
|
+
/** File path (relative to repo root for internal, URL/package ref for external) */
|
|
239
|
+
path: string;
|
|
240
|
+
|
|
241
|
+
/** How this file relates to the canvas */
|
|
242
|
+
role: CanvasFileRole;
|
|
243
|
+
|
|
244
|
+
/** Whether the file is in this repo or external */
|
|
245
|
+
origin: CanvasFileOrigin;
|
|
246
|
+
|
|
247
|
+
/** Where this file reference was defined */
|
|
248
|
+
level: CanvasFileLevel;
|
|
249
|
+
|
|
250
|
+
/** IDs of nodes that reference this file (for canvas-node level) */
|
|
251
|
+
nodeIds: string[];
|
|
252
|
+
|
|
253
|
+
/** Node types that reference this file */
|
|
254
|
+
nodeTypes: string[];
|
|
255
|
+
|
|
256
|
+
/** Workflow IDs that reference this file (for workflow level) */
|
|
257
|
+
workflowIds: string[];
|
|
258
|
+
|
|
259
|
+
/** Event names associated with this file's nodes */
|
|
260
|
+
eventNames: string[];
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Canvas-level statistics
|
|
265
|
+
*/
|
|
266
|
+
export interface CanvasFileStats {
|
|
267
|
+
totalFiles: number;
|
|
268
|
+
internalFiles: number;
|
|
269
|
+
externalFiles: number;
|
|
270
|
+
instrumentationFiles: number;
|
|
271
|
+
referenceFiles: number;
|
|
272
|
+
nodesWithFiles: number;
|
|
273
|
+
nodesWithoutFiles: number;
|
|
274
|
+
uniqueEventNames: number;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Storyboard-level statistics
|
|
279
|
+
*/
|
|
280
|
+
export interface StoryboardFileStats extends CanvasFileStats {
|
|
281
|
+
workflowCount: number;
|
|
282
|
+
scenarioCount: number;
|
|
283
|
+
rootSpanFiles: number;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* File manifest for a single canvas (without workflows)
|
|
288
|
+
*/
|
|
289
|
+
export interface CanvasFileManifest {
|
|
290
|
+
/** Canvas ID */
|
|
291
|
+
canvasId: string;
|
|
292
|
+
|
|
293
|
+
/** Canvas path */
|
|
294
|
+
canvasPath: string;
|
|
295
|
+
|
|
296
|
+
/** Canvas type */
|
|
297
|
+
canvasType: CanvasType;
|
|
298
|
+
|
|
299
|
+
/** All files from canvas nodes, deduplicated */
|
|
300
|
+
files: CanvasFile[];
|
|
301
|
+
|
|
302
|
+
/** Quick lookup by role */
|
|
303
|
+
byRole: Record<CanvasFileRole, CanvasFile[]>;
|
|
304
|
+
|
|
305
|
+
/** Quick lookup by origin */
|
|
306
|
+
byOrigin: Record<CanvasFileOrigin, CanvasFile[]>;
|
|
307
|
+
|
|
308
|
+
/** Reverse mapping: file path -> node IDs */
|
|
309
|
+
fileToNodes: Map<string, string[]>;
|
|
310
|
+
|
|
311
|
+
/** Reverse mapping: node ID -> file paths */
|
|
312
|
+
nodeToFiles: Map<string, string[]>;
|
|
313
|
+
|
|
314
|
+
/** Reverse mapping: event name -> file paths */
|
|
315
|
+
eventToFiles: Map<string, string[]>;
|
|
316
|
+
|
|
317
|
+
/** Summary statistics */
|
|
318
|
+
stats: CanvasFileStats;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* File manifest for a workflow (includes canvas + workflow-level files)
|
|
323
|
+
*/
|
|
324
|
+
export interface WorkflowFileManifest extends CanvasFileManifest {
|
|
325
|
+
/** Workflow ID */
|
|
326
|
+
workflowId: string;
|
|
327
|
+
|
|
328
|
+
/** Workflow path */
|
|
329
|
+
workflowPath: string;
|
|
330
|
+
|
|
331
|
+
/** Root span name */
|
|
332
|
+
rootSpan?: string;
|
|
333
|
+
|
|
334
|
+
/** Files from workflow.files property */
|
|
335
|
+
workflowFiles: CanvasFile[];
|
|
336
|
+
|
|
337
|
+
/** Merged files (canvas + workflow) */
|
|
338
|
+
allFiles: CanvasFile[];
|
|
339
|
+
|
|
340
|
+
/** Files filtered by scenario (scenario ID -> files) */
|
|
341
|
+
byScenario: Map<string, CanvasFile[]>;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* File manifest for an entire storyboard
|
|
346
|
+
*/
|
|
347
|
+
export interface StoryboardFileManifest {
|
|
348
|
+
/** Storyboard ID */
|
|
349
|
+
storyboardId: string;
|
|
350
|
+
|
|
351
|
+
/** Storyboard path */
|
|
352
|
+
storyboardPath: string;
|
|
353
|
+
|
|
354
|
+
/** Main canvas manifest */
|
|
355
|
+
canvas: CanvasFileManifest;
|
|
356
|
+
|
|
357
|
+
/** Workflow manifests */
|
|
358
|
+
workflows: WorkflowFileManifest[];
|
|
359
|
+
|
|
360
|
+
/** All files across canvas and all workflows */
|
|
361
|
+
allFiles: CanvasFile[];
|
|
362
|
+
|
|
363
|
+
/** Summary statistics */
|
|
364
|
+
stats: StoryboardFileStats;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Discovered canvas with content AND file manifest
|
|
369
|
+
*/
|
|
370
|
+
export interface DiscoveredCanvasWithManifest extends DiscoveredCanvasWithContent {
|
|
371
|
+
manifest: CanvasFileManifest;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Discovered workflow with content AND file manifest
|
|
376
|
+
*/
|
|
377
|
+
export interface DiscoveredWorkflowWithManifest extends DiscoveredWorkflowWithContent {
|
|
378
|
+
manifest: WorkflowFileManifest;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Discovered storyboard with content AND file manifest
|
|
383
|
+
*/
|
|
384
|
+
export interface DiscoveredStoryboardWithManifest extends DiscoveredStoryboardWithContent {
|
|
385
|
+
manifest: StoryboardFileManifest;
|
|
386
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -282,8 +282,28 @@ export type {
|
|
|
282
282
|
DiscoveredWorkflowWithContent,
|
|
283
283
|
DiscoveredStoryboardWithContent,
|
|
284
284
|
CanvasDiscoveryResultWithContent,
|
|
285
|
+
// File manifest types
|
|
286
|
+
CanvasFileRole,
|
|
287
|
+
CanvasFileOrigin,
|
|
288
|
+
CanvasFileLevel,
|
|
289
|
+
CanvasFile,
|
|
290
|
+
CanvasFileStats,
|
|
291
|
+
StoryboardFileStats,
|
|
292
|
+
CanvasFileManifest,
|
|
293
|
+
WorkflowFileManifest,
|
|
294
|
+
StoryboardFileManifest,
|
|
295
|
+
DiscoveredCanvasWithManifest,
|
|
296
|
+
DiscoveredWorkflowWithManifest,
|
|
297
|
+
DiscoveredStoryboardWithManifest,
|
|
285
298
|
} from './discovery/types';
|
|
286
299
|
|
|
300
|
+
// Export file manifest builders (browser-safe)
|
|
301
|
+
export {
|
|
302
|
+
buildCanvasFileManifest,
|
|
303
|
+
buildWorkflowFileManifest,
|
|
304
|
+
buildStoryboardFileManifest,
|
|
305
|
+
} from './discovery/CanvasFileManifest';
|
|
306
|
+
|
|
287
307
|
// Export scopes module (canvas validation + scope utilities)
|
|
288
308
|
export {
|
|
289
309
|
ScopesCanvasValidator,
|
|
@@ -403,7 +423,6 @@ export type {
|
|
|
403
423
|
} from './storyboard/index';
|
|
404
424
|
export {
|
|
405
425
|
buildStoryboardContext,
|
|
406
|
-
buildNodeSourcesMap,
|
|
407
426
|
buildEventNodeMap,
|
|
408
427
|
getNodeEventName,
|
|
409
428
|
resolveScenarioNodeIds,
|
|
@@ -13,6 +13,8 @@ import type {
|
|
|
13
13
|
ScenarioReference,
|
|
14
14
|
StoryboardContextSliceData,
|
|
15
15
|
} from './types';
|
|
16
|
+
import type { CanvasType } from '../discovery/types';
|
|
17
|
+
import { buildCanvasFileManifest } from '../discovery/CanvasFileManifest';
|
|
16
18
|
|
|
17
19
|
// ============================================================================
|
|
18
20
|
// Types
|
|
@@ -33,6 +35,9 @@ export interface BuildStoryboardContextOptions {
|
|
|
33
35
|
/** Storyboard metadata (id, name, path) */
|
|
34
36
|
storyboard: StoryboardReference;
|
|
35
37
|
|
|
38
|
+
/** Canvas type (defaults to 'otel') */
|
|
39
|
+
canvasType?: CanvasType;
|
|
40
|
+
|
|
36
41
|
/** Optional: Selected workflow template and its path */
|
|
37
42
|
workflow?: {
|
|
38
43
|
template: WorkflowTemplate;
|
|
@@ -50,37 +55,6 @@ export interface BuildStoryboardContextOptions {
|
|
|
50
55
|
// Core Functions
|
|
51
56
|
// ============================================================================
|
|
52
57
|
|
|
53
|
-
/**
|
|
54
|
-
* Build a map of canvas node ID to reference paths.
|
|
55
|
-
* Extracts `pv.references` (or deprecated `pv.sources`) from each node.
|
|
56
|
-
*
|
|
57
|
-
* @param canvas - The extended canvas with nodes
|
|
58
|
-
* @returns Record mapping node IDs to their reference paths
|
|
59
|
-
*
|
|
60
|
-
* @example
|
|
61
|
-
* ```typescript
|
|
62
|
-
* const nodeSources = buildNodeSourcesMap(canvas);
|
|
63
|
-
* // { 'node-abc': ['src/auth/login.ts'], 'node-xyz': ['@logfire/pydantic-ai'] }
|
|
64
|
-
* ```
|
|
65
|
-
*/
|
|
66
|
-
export function buildNodeSourcesMap(canvas: ExtendedCanvas): Record<string, string[]> {
|
|
67
|
-
const nodeSources: Record<string, string[]> = {};
|
|
68
|
-
|
|
69
|
-
if (!canvas.nodes) {
|
|
70
|
-
return nodeSources;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
for (const node of canvas.nodes) {
|
|
74
|
-
// Prefer references, fall back to deprecated sources
|
|
75
|
-
const refs = node.pv?.references ?? node.pv?.sources;
|
|
76
|
-
if (refs && refs.length > 0) {
|
|
77
|
-
nodeSources[node.id] = [...refs];
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return nodeSources;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
58
|
/**
|
|
85
59
|
* Build a map of event name to canvas node IDs.
|
|
86
60
|
* Uses `pv.event.name` or `pv.eventRef` from each node.
|
|
@@ -267,10 +241,10 @@ export function getAllNodeIds(canvas: ExtendedCanvas): string[] {
|
|
|
267
241
|
export function buildStoryboardContext(
|
|
268
242
|
options: BuildStoryboardContextOptions
|
|
269
243
|
): StoryboardContextSliceData {
|
|
270
|
-
const { canvas, storyboard, workflow, scenario, additionalSupportingFiles = [] } = options;
|
|
244
|
+
const { canvas, storyboard, canvasType = 'otel', workflow, scenario, additionalSupportingFiles = [] } = options;
|
|
271
245
|
|
|
272
|
-
// Build
|
|
273
|
-
const
|
|
246
|
+
// Build canvas file manifest
|
|
247
|
+
const manifest = buildCanvasFileManifest(canvas, storyboard.id, storyboard.path, canvasType);
|
|
274
248
|
|
|
275
249
|
// Build supporting files list
|
|
276
250
|
const supportingFiles: string[] = [storyboard.path];
|
|
@@ -310,7 +284,7 @@ export function buildStoryboardContext(
|
|
|
310
284
|
storyboard,
|
|
311
285
|
workflow: workflowReference,
|
|
312
286
|
scenario: scenarioReference,
|
|
313
|
-
|
|
287
|
+
manifest,
|
|
314
288
|
supportingFiles,
|
|
315
289
|
};
|
|
316
290
|
}
|