@principal-ai/principal-view-react 0.16.36 → 0.16.38

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.
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Multi-repo path handling for subsystem graphs.
3
+ *
4
+ * A component's `file` is relative to ITS OWN repo root — the repo identified
5
+ * by its `purl` (`pkg:github/<owner>/<name>[#<path>]`) — never to the machine.
6
+ * When a graph spans several repos the sidebar tree needs synthetic
7
+ * `<owner>/<name>/` prefixes to keep same-named paths apart; hosts resolve
8
+ * each file against `repoRoots[purlRepo] ?? repoRoot`.
9
+ */
10
+
11
+ /** Normalize a purl to its repo key: fragment and surrounding whitespace stripped. */
12
+ export function purlRepoKey(purl: string | undefined): string | undefined {
13
+ if (!purl) return undefined;
14
+ const base = purl.split('#')[0]?.trim();
15
+ return base || undefined;
16
+ }
17
+
18
+ /** `owner/name` from a purl (`pkg:github/acme/widget#x/y.ts` → `acme/widget`). */
19
+ export function purlOwnerName(purl: string | undefined): string | undefined {
20
+ const key = purlRepoKey(purl);
21
+ if (!key) return undefined;
22
+ // pkg:<type>/<owner>/<name> → take the last two segments.
23
+ const parts = key.split('/').filter(Boolean);
24
+ if (parts.length < 2) return undefined;
25
+ return `${parts[parts.length - 2]}/${parts[parts.length - 1]}`;
26
+ }
27
+
28
+ export interface TreeEntry {
29
+ /** Path as shown in the sidebar tree (repo-prefixed when multi-repo). */
30
+ displayPath: string;
31
+ /** The component's own file value — what hosts resolve + drawers open. */
32
+ file: string;
33
+ }
34
+
35
+ export interface TreePathMapping {
36
+ entries: TreeEntry[];
37
+ /** True when components span more than one repo (prefixes are shown). */
38
+ multiRepo: boolean;
39
+ /** displayPath → file. */
40
+ toFile: Map<string, string>;
41
+ /** file → displayPath (first component wins). */
42
+ toDisplay: Map<string, string>;
43
+ }
44
+
45
+ /**
46
+ * Build the sidebar tree's path set from components. Single-repo graphs (and
47
+ * purl-less components) keep bare `file` values; mixed-repo graphs prefix each
48
+ * entry with its purl's `owner/name` so the tree groups by repo.
49
+ */
50
+ export function buildTreePathMapping(components: ReadonlyArray<{ file?: string; purl?: string }>): TreePathMapping {
51
+ const withFiles = components.filter((c) => c.file);
52
+ const repos = new Set(
53
+ withFiles.map((c) => purlRepoKey(c.purl)).filter((k): k is string => !!k),
54
+ );
55
+ const multiRepo = repos.size > 1;
56
+
57
+ const entries: TreeEntry[] = [];
58
+ const toFile = new Map<string, string>();
59
+ const toDisplay = new Map<string, string>();
60
+ for (const c of withFiles) {
61
+ const ownerName = multiRepo ? purlOwnerName(c.purl) : undefined;
62
+ const displayPath = ownerName ? `${ownerName}/${c.file}` : c.file!;
63
+ if (toFile.has(displayPath)) continue;
64
+ entries.push({ displayPath, file: c.file! });
65
+ toFile.set(displayPath, c.file!);
66
+ if (!toDisplay.has(c.file!)) toDisplay.set(c.file!, displayPath);
67
+ }
68
+ return { entries, multiRepo, toFile, toDisplay };
69
+ }
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // Per-repo tree grouping — one sidebar tree per repo, each under its own
73
+ // styled header (owner avatar + repo name).
74
+ // ---------------------------------------------------------------------------
75
+
76
+ export interface RepoGroup {
77
+ /** Purl repo key (`pkg:github/owner/name`) — undefined for purl-less components. */
78
+ repoKey?: string;
79
+ /** `owner` segment of the purl, when present. */
80
+ owner?: string;
81
+ /** Repository name segment of the purl, when present. */
82
+ repo?: string;
83
+ /** The group's files, bare `file` values (each tree scopes its own paths). */
84
+ entries: TreeEntry[];
85
+ }
86
+
87
+ /**
88
+ * Group components by repo for the multi-tree sidebar. Groups preserve
89
+ * first-appearance order; purl-less components land in a trailing unbadged
90
+ * group. Entries carry bare `file` values — separate trees scope their own
91
+ * path sets, so no synthetic prefixes are needed.
92
+ */
93
+ export function buildRepoGroups(components: ReadonlyArray<{ file?: string; purl?: string }>): {
94
+ multiRepo: boolean;
95
+ groups: RepoGroup[];
96
+ } {
97
+ const withFiles = components.filter((c) => c.file);
98
+ const repos = new Set(
99
+ withFiles.map((c) => purlRepoKey(c.purl)).filter((k): k is string => !!k),
100
+ );
101
+ const multiRepo = repos.size > 1;
102
+
103
+ const byKey = new Map<string, RepoGroup>();
104
+ const order: Array<string | undefined> = [];
105
+ for (const c of withFiles) {
106
+ const key = purlRepoKey(c.purl);
107
+ let group = byKey.get(key ?? '');
108
+ if (!group) {
109
+ const ownerName = key ? purlOwnerName(c.purl) : undefined;
110
+ const [owner, repo] = ownerName?.split('/') ?? [];
111
+ group = { repoKey: key, owner, repo, entries: [] };
112
+ byKey.set(key ?? '', group);
113
+ order.push(key);
114
+ }
115
+ if (!group.entries.some((e) => e.file === c.file)) {
116
+ group.entries.push({ displayPath: c.file!, file: c.file! });
117
+ }
118
+ }
119
+ return { multiRepo, groups: order.map((k) => byKey.get(k ?? '')!) };
120
+ }
121
+
122
+ /** GitHub owner-avatar URL for a purl, or undefined for non-github hosts. */
123
+ export function repoAvatarUrl(purl: string | undefined): string | undefined {
124
+ const key = purlRepoKey(purl);
125
+ const match = key?.match(/^pkg:github\/([^/]+)\//);
126
+ return match ? `https://github.com/${match[1]}.png?size=64` : undefined;
127
+ }