@narumitw/pi-statusline 0.50.0 → 0.50.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/README.md +29 -209
- package/dist/chunks/{chunk-74QZGJ7L.js → chunk-J2ZSGEDQ.js} +18 -97
- package/dist/chunks/chunk-J2ZSGEDQ.js.map +7 -0
- package/dist/chunks/{commands-C2G2G7C6.js → commands-PPT3AJTL.js} +14 -58
- package/dist/chunks/commands-PPT3AJTL.js.map +7 -0
- package/dist/index.ts +21 -76
- package/dist/index.ts.map +2 -2
- package/docs/configuration.md +194 -0
- package/package.json +51 -51
- package/src/ansi.ts +18 -24
- package/src/command-contract.ts +6 -6
- package/src/commands.ts +759 -818
- package/src/directory.ts +27 -31
- package/src/extension-status.ts +186 -219
- package/src/git-status.ts +103 -111
- package/src/information-profiles.ts +12 -31
- package/src/powerline.ts +120 -127
- package/src/presets/candy.ts +1 -7
- package/src/presets/create-ramp.ts +21 -23
- package/src/presets/custom.ts +7 -7
- package/src/presets/forest.ts +1 -7
- package/src/presets/index.ts +30 -32
- package/src/presets/mono.ts +1 -7
- package/src/presets/neon.ts +1 -7
- package/src/presets/ocean.ts +1 -7
- package/src/presets/sunset.ts +1 -7
- package/src/presets/tokyo-night.ts +9 -9
- package/src/presets/types.ts +5 -5
- package/src/render.ts +277 -306
- package/src/settings.ts +516 -568
- package/src/statusline.ts +347 -372
- package/src/types.ts +34 -42
- package/src/usage.ts +40 -40
- package/dist/chunks/chunk-74QZGJ7L.js.map +0 -7
- package/dist/chunks/commands-C2G2G7C6.js.map +0 -7
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
24
|
+
return sep === "\\" ? value.replaceAll("\\", "/") : value;
|
|
29
25
|
}
|
|
30
26
|
|
|
31
27
|
function contractPath(path: string, root: string | undefined, replacement: string): string {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
49
|
+
return relative(left, right) === "";
|
|
54
50
|
}
|
package/src/extension-status.ts
CHANGED
|
@@ -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
|
-
|
|
12
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
63
|
+
key: string,
|
|
64
|
+
leadingIcon: string | undefined,
|
|
65
|
+
configuredIcons: Record<string, string>,
|
|
66
|
+
extensionStatusIconAliases: ExtensionStatusIconAliasMap,
|
|
80
67
|
) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
110
|
-
|
|
93
|
+
key: string,
|
|
94
|
+
extensionStatusIconAliases: ExtensionStatusIconAliasMap,
|
|
111
95
|
): readonly string[] {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
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
|
-
|
|
124
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
146
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
156
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
174
157
|
}
|
|
175
158
|
|
|
176
159
|
interface InstalledExtensionPackage {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
160
|
+
packageName: string;
|
|
161
|
+
source: string;
|
|
162
|
+
identity: string;
|
|
180
163
|
}
|
|
181
164
|
|
|
182
165
|
export function readInstalledExtensionPackages(cwd: string): InstalledExtensionPackage[] {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
202
|
+
installedPackages: readonly { packageName: string; source?: string }[],
|
|
224
203
|
): Map<string, string[]> {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
253
|
-
|
|
223
|
+
packageName: string,
|
|
224
|
+
source?: string,
|
|
254
225
|
): { statusBase: string; aliases: string[] } | undefined {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
268
|
-
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
|
|
249
|
+
return [...new Set(values.filter((value) => value.length > 0))];
|
|
279
250
|
}
|
|
280
251
|
|
|
281
252
|
function readPackageSources(settingsFile: string): string[] {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
-
|
|
321
|
-
|
|
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
|
-
|
|
292
|
+
return isAbsolute(source) ? source : resolve(baseDirectory, source);
|
|
326
293
|
}
|