@narumitw/pi-statusline 0.50.0 → 0.50.2

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/src/directory.ts CHANGED
@@ -5,50 +5,46 @@ const DEFAULT_TRUNCATION_LENGTH = 3;
5
5
  const DEFAULT_HOME_SYMBOL = "~";
6
6
 
7
7
  /** Apply Starship's default directory presentation without exposing a format language. */
8
- export function formatDirectoryPath(
9
- cwd: string,
10
- homeDir: string | undefined,
11
- gitRoot: string | undefined,
12
- ): string {
13
- const contracted =
14
- gitRoot && (!homeDir || !samePath(gitRoot, homeDir))
15
- ? (contractRepositoryPath(cwd, gitRoot) ?? contractPath(cwd, homeDir, DEFAULT_HOME_SYMBOL))
16
- : contractPath(cwd, homeDir, DEFAULT_HOME_SYMBOL);
17
- const safeContracted = sanitizeTerminalText(contracted);
18
- const components = safeContracted.split("/").filter((component) => component.length > 0);
19
- const displayed =
20
- components.length > DEFAULT_TRUNCATION_LENGTH
21
- ? components.slice(-DEFAULT_TRUNCATION_LENGTH).join("/")
22
- : safeContracted;
23
- const native = sep === "/" ? displayed : displayed.replaceAll("/", sep);
24
- return sanitizeTerminalText(native || basename(cwd) || cwd);
8
+ export function formatDirectoryPath(cwd: string, homeDir: string | undefined, gitRoot: string | undefined): string {
9
+ const contracted =
10
+ gitRoot && (!homeDir || !samePath(gitRoot, homeDir))
11
+ ? (contractRepositoryPath(cwd, gitRoot) ?? contractPath(cwd, homeDir, DEFAULT_HOME_SYMBOL))
12
+ : contractPath(cwd, homeDir, DEFAULT_HOME_SYMBOL);
13
+ const safeContracted = sanitizeTerminalText(contracted);
14
+ const components = safeContracted.split("/").filter((component) => component.length > 0);
15
+ const displayed =
16
+ components.length > DEFAULT_TRUNCATION_LENGTH
17
+ ? components.slice(-DEFAULT_TRUNCATION_LENGTH).join("/")
18
+ : safeContracted;
19
+ const native = sep === "/" ? displayed : displayed.replaceAll("/", sep);
20
+ return sanitizeTerminalText(native || basename(cwd) || cwd);
25
21
  }
26
22
 
27
23
  function toSlashPath(value: string): string {
28
- return sep === "\\" ? value.replaceAll("\\", "/") : value;
24
+ return sep === "\\" ? value.replaceAll("\\", "/") : value;
29
25
  }
30
26
 
31
27
  function contractPath(path: string, root: string | undefined, replacement: string): string {
32
- if (!root) return toSlashPath(path);
33
- const child = relativeWithin(path, root);
34
- if (child === undefined) return toSlashPath(path);
35
- return child ? `${replacement}/${child}` : replacement;
28
+ if (!root) return toSlashPath(path);
29
+ const child = relativeWithin(path, root);
30
+ if (child === undefined) return toSlashPath(path);
31
+ return child ? `${replacement}/${child}` : replacement;
36
32
  }
37
33
 
38
34
  function contractRepositoryPath(path: string, root: string): string | undefined {
39
- const child = relativeWithin(path, root);
40
- if (child === undefined) return undefined;
41
- const name = basename(root) || toSlashPath(root);
42
- return child ? `${name}/${child}` : name;
35
+ const child = relativeWithin(path, root);
36
+ if (child === undefined) return undefined;
37
+ const name = basename(root) || toSlashPath(root);
38
+ return child ? `${name}/${child}` : name;
43
39
  }
44
40
 
45
41
  function relativeWithin(path: string, root: string): string | undefined {
46
- const child = relative(root, path);
47
- if (child === "") return "";
48
- if (child === ".." || child.startsWith(`..${sep}`) || isAbsolute(child)) return undefined;
49
- return toSlashPath(child);
42
+ const child = relative(root, path);
43
+ if (child === "") return "";
44
+ if (child === ".." || child.startsWith(`..${sep}`) || isAbsolute(child)) return undefined;
45
+ return toSlashPath(child);
50
46
  }
51
47
 
52
48
  function samePath(left: string, right: string): boolean {
53
- return relative(left, right) === "";
49
+ return relative(left, right) === "";
54
50
  }
@@ -8,319 +8,286 @@ import type { StatuslineConfig } from "./types.js";
8
8
 
9
9
  export type ExtensionStatusIconAliasMap = ReadonlyMap<string, readonly string[]>;
10
10
  export interface ExtensionStatusRuntime {
11
- duplicateExtensions: string[];
12
- extensionStatusIconAliases: ExtensionStatusIconAliasMap;
11
+ duplicateExtensions: string[];
12
+ extensionStatusIconAliases: ExtensionStatusIconAliasMap;
13
13
  }
14
14
 
15
15
  const STATUSLINE_KEY = "statusline";
16
16
  const COMPATIBLE_STATUS_ICON_KEYS: Readonly<Record<string, string>> = {
17
- retry: "unknown-error-retry",
18
- sync: "pisync",
19
- "unknown-error-retry": "retry",
20
- pisync: "sync",
17
+ retry: "unknown-error-retry",
18
+ sync: "pisync",
19
+ "unknown-error-retry": "retry",
20
+ pisync: "sync",
21
21
  };
22
22
  const EMPTY_EXTENSION_STATUS_ICON_ALIASES: ExtensionStatusIconAliasMap = new Map();
23
- function extensionStatusSeparator(
24
- config: StatuslineConfig,
25
- theme: Theme,
26
- trueColor: boolean,
27
- ): string {
28
- return powerlineExtensionSeparator(theme, config.palettePreset, trueColor);
23
+ function extensionStatusSeparator(config: StatuslineConfig, theme: Theme, trueColor: boolean): string {
24
+ return powerlineExtensionSeparator(theme, config.palettePreset, trueColor);
29
25
  }
30
26
 
31
27
  export function formatExtensionStatuses(
32
- statuses: ReadonlyMap<string, string>,
33
- theme: Theme,
34
- config: StatuslineConfig,
35
- runtime: ExtensionStatusRuntime,
36
- hiddenKeys: ReadonlySet<string> = new Set(),
37
- trueColor = true,
28
+ statuses: ReadonlyMap<string, string>,
29
+ theme: Theme,
30
+ config: StatuslineConfig,
31
+ runtime: ExtensionStatusRuntime,
32
+ hiddenKeys: ReadonlySet<string> = new Set(),
33
+ trueColor = true,
38
34
  ): string {
39
- const separator = extensionStatusSeparator(config, theme, trueColor);
40
- const visibleStatuses = [
41
- ...formatDuplicateExtensionStatus(runtime, theme),
42
- ...[...statuses.entries()]
43
- .filter(
44
- ([key, value]) => key !== STATUSLINE_KEY && !hiddenKeys.has(key) && value.trim().length > 0,
45
- )
46
- .map(([key, value]) =>
47
- formatExtensionStatus(key, value, theme, config, runtime.extensionStatusIconAliases),
48
- ),
49
- ].slice(0, 5);
50
-
51
- return visibleStatuses.join(separator);
35
+ const separator = extensionStatusSeparator(config, theme, trueColor);
36
+ const visibleStatuses = [
37
+ ...formatDuplicateExtensionStatus(runtime, theme),
38
+ ...[...statuses.entries()]
39
+ .filter(([key, value]) => key !== STATUSLINE_KEY && !hiddenKeys.has(key) && value.trim().length > 0)
40
+ .map(([key, value]) => formatExtensionStatus(key, value, theme, config, runtime.extensionStatusIconAliases)),
41
+ ].slice(0, 5);
42
+
43
+ return visibleStatuses.join(separator);
52
44
  }
53
45
 
54
46
  export function formatExtensionStatus(
55
- key: string,
56
- value: string,
57
- theme: Theme,
58
- config: Pick<StatuslineConfig, "extensionStatusIcons">,
59
- extensionStatusIconAliases: ExtensionStatusIconAliasMap = EMPTY_EXTENSION_STATUS_ICON_ALIASES,
47
+ key: string,
48
+ value: string,
49
+ theme: Theme,
50
+ config: Pick<StatuslineConfig, "extensionStatusIcons">,
51
+ extensionStatusIconAliases: ExtensionStatusIconAliasMap = EMPTY_EXTENSION_STATUS_ICON_ALIASES,
60
52
  ): string {
61
- const status = splitExtensionStatusIcon(stripExtensionStatusPrefix(key, value));
62
- const text = simplifyExtensionStatusText(status.text);
63
- const color = extensionColor(key, value);
64
- const textColor = color === "warning" ? "warning" : "muted";
65
- const icon = extensionStatusIcon(
66
- key,
67
- status.icon,
68
- config.extensionStatusIcons,
69
- extensionStatusIconAliases,
70
- );
71
- const renderedText = theme.fg(textColor, text);
72
- return icon ? `${theme.fg(color, icon)} ${renderedText}` : renderedText;
53
+ const status = splitExtensionStatusIcon(stripExtensionStatusPrefix(key, value));
54
+ const text = simplifyExtensionStatusText(status.text);
55
+ const color = extensionColor(key, value);
56
+ const textColor = color === "warning" ? "warning" : "muted";
57
+ const icon = extensionStatusIcon(key, status.icon, config.extensionStatusIcons, extensionStatusIconAliases);
58
+ const renderedText = theme.fg(textColor, text);
59
+ return icon ? `${theme.fg(color, icon)} ${renderedText}` : renderedText;
73
60
  }
74
61
 
75
62
  function extensionStatusIcon(
76
- key: string,
77
- leadingIcon: string | undefined,
78
- configuredIcons: Record<string, string>,
79
- extensionStatusIconAliases: ExtensionStatusIconAliasMap,
63
+ key: string,
64
+ leadingIcon: string | undefined,
65
+ configuredIcons: Record<string, string>,
66
+ extensionStatusIconAliases: ExtensionStatusIconAliasMap,
80
67
  ) {
81
- if (Object.hasOwn(configuredIcons, key)) return configuredIcons[key];
82
- const namespaceIcon = configuredNamespaceIcon(key, configuredIcons);
83
- if (namespaceIcon !== undefined) return namespaceIcon;
84
- const compatibleKey = COMPATIBLE_STATUS_ICON_KEYS[key];
85
- if (compatibleKey && Object.hasOwn(configuredIcons, compatibleKey)) {
86
- return configuredIcons[compatibleKey];
87
- }
88
- for (const alias of extensionStatusAliasesForKey(key, extensionStatusIconAliases)) {
89
- if (Object.hasOwn(configuredIcons, alias)) return configuredIcons[alias];
90
- }
91
- return leadingIcon ?? DEFAULT_EXTENSION_STATUS_ICONS[key] ?? "🔌";
68
+ if (Object.hasOwn(configuredIcons, key)) return configuredIcons[key];
69
+ const namespaceIcon = configuredNamespaceIcon(key, configuredIcons);
70
+ if (namespaceIcon !== undefined) return namespaceIcon;
71
+ const compatibleKey = COMPATIBLE_STATUS_ICON_KEYS[key];
72
+ if (compatibleKey && Object.hasOwn(configuredIcons, compatibleKey)) {
73
+ return configuredIcons[compatibleKey];
74
+ }
75
+ for (const alias of extensionStatusAliasesForKey(key, extensionStatusIconAliases)) {
76
+ if (Object.hasOwn(configuredIcons, alias)) return configuredIcons[alias];
77
+ }
78
+ return leadingIcon ?? DEFAULT_EXTENSION_STATUS_ICONS[key] ?? "🔌";
92
79
  }
93
80
 
94
- function configuredNamespaceIcon(
95
- key: string,
96
- configuredIcons: Readonly<Record<string, string>>,
97
- ): string | undefined {
98
- let match: { baseLength: number; icon: string } | undefined;
99
- for (const [selector, icon] of Object.entries(configuredIcons)) {
100
- if (!selector.endsWith(":*")) continue;
101
- const base = selector.slice(0, -2);
102
- if (!base || !key.startsWith(`${base}:`)) continue;
103
- if (!match || base.length > match.baseLength) match = { baseLength: base.length, icon };
104
- }
105
- return match?.icon;
81
+ function configuredNamespaceIcon(key: string, configuredIcons: Readonly<Record<string, string>>): string | undefined {
82
+ let match: { baseLength: number; icon: string } | undefined;
83
+ for (const [selector, icon] of Object.entries(configuredIcons)) {
84
+ if (!selector.endsWith(":*")) continue;
85
+ const base = selector.slice(0, -2);
86
+ if (!base || !key.startsWith(`${base}:`)) continue;
87
+ if (!match || base.length > match.baseLength) match = { baseLength: base.length, icon };
88
+ }
89
+ return match?.icon;
106
90
  }
107
91
 
108
92
  function extensionStatusAliasesForKey(
109
- key: string,
110
- extensionStatusIconAliases: ExtensionStatusIconAliasMap,
93
+ key: string,
94
+ extensionStatusIconAliases: ExtensionStatusIconAliasMap,
111
95
  ): readonly string[] {
112
- for (const [statusBase, aliases] of extensionStatusIconAliases) {
113
- if (statusKeyMatchesStatusBase(key, statusBase)) return aliases;
114
- }
115
- return [];
96
+ for (const [statusBase, aliases] of extensionStatusIconAliases) {
97
+ if (statusKeyMatchesStatusBase(key, statusBase)) return aliases;
98
+ }
99
+ return [];
116
100
  }
117
101
 
118
102
  function statusKeyMatchesStatusBase(key: string, statusBase: string): boolean {
119
- return key === statusBase || key.startsWith(`${statusBase}:`) || key.startsWith(`${statusBase}/`);
103
+ return key === statusBase || key.startsWith(`${statusBase}:`) || key.startsWith(`${statusBase}/`);
120
104
  }
121
105
 
122
106
  export function wrapExtensionStatusline(status: string, width: number): string[] {
123
- if (!status || width <= 0) return [];
124
- return wrapTextWithAnsi(status, width);
107
+ if (!status || width <= 0) return [];
108
+ return wrapTextWithAnsi(status, width);
125
109
  }
126
110
 
127
111
  function formatDuplicateExtensionStatus(runtime: ExtensionStatusRuntime, theme: Theme): string[] {
128
- if (runtime.duplicateExtensions.length === 0) return [];
129
- const names = runtime.duplicateExtensions.slice(0, 2).join(", ");
130
- const suffix =
131
- runtime.duplicateExtensions.length > 2 ? ` +${runtime.duplicateExtensions.length - 2}` : "";
132
- return [`${theme.fg("warning", "⚠️")} ${theme.fg("warning", `dup ${names}${suffix}`)}`];
112
+ if (runtime.duplicateExtensions.length === 0) return [];
113
+ const names = runtime.duplicateExtensions.slice(0, 2).join(", ");
114
+ const suffix = runtime.duplicateExtensions.length > 2 ? ` +${runtime.duplicateExtensions.length - 2}` : "";
115
+ return [`${theme.fg("warning", "⚠️")} ${theme.fg("warning", `dup ${names}${suffix}`)}`];
133
116
  }
134
117
 
135
118
  export function splitExtensionStatusIcon(value: string): { icon?: string; text: string } {
136
- const trimmed = value.trim();
137
- const [firstToken, ...restTokens] = trimmed.split(/\s+/);
138
- if (firstToken && isEmojiOnlyToken(firstToken)) {
139
- return { icon: firstToken, text: restTokens.join(" ") };
140
- }
141
- return { text: trimmed };
119
+ const trimmed = value.trim();
120
+ const [firstToken, ...restTokens] = trimmed.split(/\s+/);
121
+ if (firstToken && isEmojiOnlyToken(firstToken)) {
122
+ return { icon: firstToken, text: restTokens.join(" ") };
123
+ }
124
+ return { text: trimmed };
142
125
  }
143
126
 
144
127
  function isEmojiOnlyToken(value: string): boolean {
145
- return /^(?=.*(?:\p{Extended_Pictographic}|\p{Regional_Indicator}|[0-9#*]\ufe0f?\u20e3))(?:\p{Extended_Pictographic}|\p{Emoji_Modifier}|\p{Regional_Indicator}|\u200d|\ufe0f|[0-9#*]\ufe0f?\u20e3)+$/u.test(
146
- value,
147
- );
128
+ return /^(?=.*(?:\p{Extended_Pictographic}|\p{Regional_Indicator}|[0-9#*]\ufe0f?\u20e3))(?:\p{Extended_Pictographic}|\p{Emoji_Modifier}|\p{Regional_Indicator}|\u200d|\ufe0f|[0-9#*]\ufe0f?\u20e3)+$/u.test(
129
+ value,
130
+ );
148
131
  }
149
132
 
150
133
  export function extensionColor(key: string, value: string): ThemeColor {
151
- const normalized = `${key} ${value}`.toLowerCase();
152
- if (/missing|error|fail|conflict|duplicate|unavailable/.test(normalized)) return "warning";
153
- if (normalized.includes("codex")) return "accent";
154
- if (/ready|active|running|enabled|awake|ok/.test(normalized)) return "success";
155
- return "muted";
134
+ const normalized = `${key} ${value}`.toLowerCase();
135
+ if (/missing|error|fail|conflict|duplicate|unavailable/.test(normalized)) return "warning";
136
+ if (normalized.includes("codex")) return "accent";
137
+ if (/ready|active|running|enabled|awake|ok/.test(normalized)) return "success";
138
+ return "muted";
156
139
  }
157
140
 
158
141
  export function stripExtensionStatusPrefix(key: string, value: string): string {
159
- return value.trim().replace(new RegExp(`^${escapeRegExp(key)}\\s*:\\s*`, "iu"), "");
142
+ return value.trim().replace(new RegExp(`^${escapeRegExp(key)}\\s*:\\s*`, "iu"), "");
160
143
  }
161
144
 
162
145
  export function simplifyExtensionStatusText(value: string): string {
163
- return value
164
- .trim()
165
- .replace(/\bready\b/giu, "✓")
166
- .replace(/\bmissing\b/giu, "✗")
167
- .replace(/,\s*/g, " ")
168
- .replace(/\s+\([^)]*\)\s*$/, "")
169
- .replace(/\s+/g, " ");
146
+ return value
147
+ .trim()
148
+ .replace(/\bready\b/giu, "✓")
149
+ .replace(/\bmissing\b/giu, "✗")
150
+ .replace(/,\s*/g, " ")
151
+ .replace(/\s+\([^)]*\)\s*$/, "")
152
+ .replace(/\s+/g, " ");
170
153
  }
171
154
 
172
155
  function escapeRegExp(value: string): string {
173
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
156
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
174
157
  }
175
158
 
176
159
  interface InstalledExtensionPackage {
177
- packageName: string;
178
- source: string;
179
- identity: string;
160
+ packageName: string;
161
+ source: string;
162
+ identity: string;
180
163
  }
181
164
 
182
165
  export function readInstalledExtensionPackages(cwd: string): InstalledExtensionPackage[] {
183
- const packages: InstalledExtensionPackage[] = [];
184
- const settingsFiles = extensionSettingsFiles(cwd);
185
-
186
- for (const settingsFile of settingsFiles) {
187
- const baseDirectory = dirname(settingsFile);
188
- for (const rawSource of readPackageSources(settingsFile)) {
189
- const source = rawSource.trim();
190
- if (!source) continue;
191
- const packageName = packageNameForSource(source, baseDirectory);
192
- if (!packageName) continue;
193
- packages.push({ packageName, source, identity: sourceIdentity(source, baseDirectory) });
194
- }
195
- }
196
-
197
- return packages;
166
+ const packages: InstalledExtensionPackage[] = [];
167
+ const settingsFiles = extensionSettingsFiles(cwd);
168
+
169
+ for (const settingsFile of settingsFiles) {
170
+ const baseDirectory = dirname(settingsFile);
171
+ for (const rawSource of readPackageSources(settingsFile)) {
172
+ const source = rawSource.trim();
173
+ if (!source) continue;
174
+ const packageName = packageNameForSource(source, baseDirectory);
175
+ if (!packageName) continue;
176
+ packages.push({ packageName, source, identity: sourceIdentity(source, baseDirectory) });
177
+ }
178
+ }
179
+
180
+ return packages;
198
181
  }
199
182
 
200
183
  function extensionSettingsFiles(cwd: string): string[] {
201
- return [join(getAgentDir(), "settings.json"), join(cwd, ".pi", "settings.json")].filter((file) =>
202
- existsSync(file),
203
- );
184
+ return [join(getAgentDir(), "settings.json"), join(cwd, ".pi", "settings.json")].filter((file) => existsSync(file));
204
185
  }
205
186
 
206
- export function findDuplicateExtensions(
207
- installedPackages: readonly InstalledExtensionPackage[],
208
- ): string[] {
209
- const sourcesByPackage = new Map<string, Set<string>>();
187
+ export function findDuplicateExtensions(installedPackages: readonly InstalledExtensionPackage[]): string[] {
188
+ const sourcesByPackage = new Map<string, Set<string>>();
210
189
 
211
- for (const extensionPackage of installedPackages) {
212
- const sources = sourcesByPackage.get(extensionPackage.packageName) ?? new Set<string>();
213
- sources.add(extensionPackage.identity);
214
- sourcesByPackage.set(extensionPackage.packageName, sources);
215
- }
190
+ for (const extensionPackage of installedPackages) {
191
+ const sources = sourcesByPackage.get(extensionPackage.packageName) ?? new Set<string>();
192
+ sources.add(extensionPackage.identity);
193
+ sourcesByPackage.set(extensionPackage.packageName, sources);
194
+ }
216
195
 
217
- return [...sourcesByPackage.entries()]
218
- .filter(([, sources]) => sources.size > 1)
219
- .map(([packageName]) => packageName.replace(/^@[^/]+\//, "").replace(/^pi-/, ""));
196
+ return [...sourcesByPackage.entries()]
197
+ .filter(([, sources]) => sources.size > 1)
198
+ .map(([packageName]) => packageName.replace(/^@[^/]+\//, "").replace(/^pi-/, ""));
220
199
  }
221
200
 
222
201
  export function buildExtensionStatusIconAliases(
223
- installedPackages: readonly { packageName: string; source?: string }[],
202
+ installedPackages: readonly { packageName: string; source?: string }[],
224
203
  ): Map<string, string[]> {
225
- const packageAliasesByStatusBase = new Map<string, Map<string, string[]>>();
226
-
227
- for (const extensionPackage of installedPackages) {
228
- const candidate = extensionStatusIconAliasCandidate(
229
- extensionPackage.packageName,
230
- extensionPackage.source,
231
- );
232
- if (!candidate) continue;
233
- const aliasesByPackage =
234
- packageAliasesByStatusBase.get(candidate.statusBase) ?? new Map<string, string[]>();
235
- const existingAliases = aliasesByPackage.get(extensionPackage.packageName) ?? [];
236
- aliasesByPackage.set(
237
- extensionPackage.packageName,
238
- uniqueStrings([...existingAliases, ...candidate.aliases]),
239
- );
240
- packageAliasesByStatusBase.set(candidate.statusBase, aliasesByPackage);
241
- }
242
-
243
- const aliases = new Map<string, string[]>();
244
- for (const [statusBase, aliasesByPackage] of packageAliasesByStatusBase) {
245
- if (aliasesByPackage.size === 1)
246
- aliases.set(statusBase, [...aliasesByPackage.values()][0] ?? []);
247
- }
248
- return aliases;
204
+ const packageAliasesByStatusBase = new Map<string, Map<string, string[]>>();
205
+
206
+ for (const extensionPackage of installedPackages) {
207
+ const candidate = extensionStatusIconAliasCandidate(extensionPackage.packageName, extensionPackage.source);
208
+ if (!candidate) continue;
209
+ const aliasesByPackage = packageAliasesByStatusBase.get(candidate.statusBase) ?? new Map<string, string[]>();
210
+ const existingAliases = aliasesByPackage.get(extensionPackage.packageName) ?? [];
211
+ aliasesByPackage.set(extensionPackage.packageName, uniqueStrings([...existingAliases, ...candidate.aliases]));
212
+ packageAliasesByStatusBase.set(candidate.statusBase, aliasesByPackage);
213
+ }
214
+
215
+ const aliases = new Map<string, string[]>();
216
+ for (const [statusBase, aliasesByPackage] of packageAliasesByStatusBase) {
217
+ if (aliasesByPackage.size === 1) aliases.set(statusBase, [...aliasesByPackage.values()][0] ?? []);
218
+ }
219
+ return aliases;
249
220
  }
250
221
 
251
222
  function extensionStatusIconAliasCandidate(
252
- packageName: string,
253
- source?: string,
223
+ packageName: string,
224
+ source?: string,
254
225
  ): { statusBase: string; aliases: string[] } | undefined {
255
- const packageBase = packageBaseName(packageName);
256
- const statusBase = statusBaseFromPackageBase(packageBase);
257
- if (!statusBase) return undefined;
258
-
259
- const sourceAliases = source?.startsWith("npm:") ? [source, `npm:${npmPackageName(source)}`] : [];
260
- return {
261
- statusBase,
262
- aliases: uniqueStrings([...sourceAliases, packageName, packageBase, statusBase]),
263
- };
226
+ const packageBase = packageBaseName(packageName);
227
+ const statusBase = statusBaseFromPackageBase(packageBase);
228
+ if (!statusBase) return undefined;
229
+
230
+ const sourceAliases = source?.startsWith("npm:") ? [source, `npm:${npmPackageName(source)}`] : [];
231
+ return {
232
+ statusBase,
233
+ aliases: uniqueStrings([...sourceAliases, packageName, packageBase, statusBase]),
234
+ };
264
235
  }
265
236
 
266
237
  function packageBaseName(packageName: string): string {
267
- const slashIndex = packageName.lastIndexOf("/");
268
- return slashIndex === -1 ? packageName : packageName.slice(slashIndex + 1);
238
+ const slashIndex = packageName.lastIndexOf("/");
239
+ return slashIndex === -1 ? packageName : packageName.slice(slashIndex + 1);
269
240
  }
270
241
 
271
242
  function statusBaseFromPackageBase(packageBase: string): string {
272
- return packageBase.startsWith("pi-") && packageBase.length > "pi-".length
273
- ? packageBase.slice("pi-".length)
274
- : packageBase;
243
+ return packageBase.startsWith("pi-") && packageBase.length > "pi-".length
244
+ ? packageBase.slice("pi-".length)
245
+ : packageBase;
275
246
  }
276
247
 
277
248
  function uniqueStrings(values: readonly string[]): string[] {
278
- return [...new Set(values.filter((value) => value.length > 0))];
249
+ return [...new Set(values.filter((value) => value.length > 0))];
279
250
  }
280
251
 
281
252
  function readPackageSources(settingsFile: string): string[] {
282
- try {
283
- const settings = JSON.parse(readFileSync(settingsFile, "utf8")) as { packages?: unknown[] };
284
- return (settings.packages ?? [])
285
- .map((entry) => {
286
- if (typeof entry === "string") return entry;
287
- if (
288
- entry &&
289
- typeof entry === "object" &&
290
- typeof (entry as { source?: unknown }).source === "string"
291
- ) {
292
- return (entry as { source: string }).source;
293
- }
294
- return undefined;
295
- })
296
- .filter((source): source is string => source !== undefined);
297
- } catch {
298
- return [];
299
- }
253
+ try {
254
+ const settings = JSON.parse(readFileSync(settingsFile, "utf8")) as { packages?: unknown[] };
255
+ return (settings.packages ?? [])
256
+ .map((entry) => {
257
+ if (typeof entry === "string") return entry;
258
+ if (entry && typeof entry === "object" && typeof (entry as { source?: unknown }).source === "string") {
259
+ return (entry as { source: string }).source;
260
+ }
261
+ return undefined;
262
+ })
263
+ .filter((source): source is string => source !== undefined);
264
+ } catch {
265
+ return [];
266
+ }
300
267
  }
301
268
 
302
269
  function packageNameForSource(source: string, baseDirectory: string): string | undefined {
303
- if (source.startsWith("npm:")) return npmPackageName(source);
304
- const packageJson = join(resolveSourcePath(source, baseDirectory), "package.json");
305
- try {
306
- const packageData = JSON.parse(readFileSync(packageJson, "utf8")) as { name?: unknown };
307
- return typeof packageData.name === "string" ? packageData.name : undefined;
308
- } catch {
309
- return undefined;
310
- }
270
+ if (source.startsWith("npm:")) return npmPackageName(source);
271
+ const packageJson = join(resolveSourcePath(source, baseDirectory), "package.json");
272
+ try {
273
+ const packageData = JSON.parse(readFileSync(packageJson, "utf8")) as { name?: unknown };
274
+ return typeof packageData.name === "string" ? packageData.name : undefined;
275
+ } catch {
276
+ return undefined;
277
+ }
311
278
  }
312
279
 
313
280
  export function npmPackageName(source: string): string {
314
- const spec = source.slice("npm:".length);
315
- if (spec.startsWith("@")) return spec.split("@").slice(0, 2).join("@");
316
- return spec.split("@")[0] ?? spec;
281
+ const spec = source.slice("npm:".length);
282
+ if (spec.startsWith("@")) return spec.split("@").slice(0, 2).join("@");
283
+ return spec.split("@")[0] ?? spec;
317
284
  }
318
285
 
319
286
  function sourceIdentity(source: string, baseDirectory: string): string {
320
- if (source.startsWith("npm:")) return `npm:${npmPackageName(source)}`;
321
- return resolveSourcePath(source, baseDirectory);
287
+ if (source.startsWith("npm:")) return `npm:${npmPackageName(source)}`;
288
+ return resolveSourcePath(source, baseDirectory);
322
289
  }
323
290
 
324
291
  function resolveSourcePath(source: string, baseDirectory: string): string {
325
- return isAbsolute(source) ? source : resolve(baseDirectory, source);
292
+ return isAbsolute(source) ? source : resolve(baseDirectory, source);
326
293
  }