@narumitw/pi-statusline 0.49.15 → 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 +47 -222
- 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 +101 -89
- package/dist/index.ts.map +3 -3
- package/docs/configuration.md +194 -0
- package/package.json +51 -54
- package/src/ansi.ts +23 -18
- 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 -214
- package/src/git-status.ts +103 -111
- package/src/information-profiles.ts +12 -31
- package/src/powerline.ts +120 -121
- 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 +291 -280
- package/src/settings.ts +516 -568
- package/src/statusline.ts +348 -341
- 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,314 +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(config: StatuslineConfig, theme: Theme): string {
|
|
24
|
-
|
|
23
|
+
function extensionStatusSeparator(config: StatuslineConfig, theme: Theme, trueColor: boolean): string {
|
|
24
|
+
return powerlineExtensionSeparator(theme, config.palettePreset, trueColor);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export function formatExtensionStatuses(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
statuses: ReadonlyMap<string, string>,
|
|
29
|
+
theme: Theme,
|
|
30
|
+
config: StatuslineConfig,
|
|
31
|
+
runtime: ExtensionStatusRuntime,
|
|
32
|
+
hiddenKeys: ReadonlySet<string> = new Set(),
|
|
33
|
+
trueColor = true,
|
|
33
34
|
): string {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
),
|
|
44
|
-
].slice(0, 5);
|
|
45
|
-
|
|
46
|
-
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);
|
|
47
44
|
}
|
|
48
45
|
|
|
49
46
|
export function formatExtensionStatus(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
key: string,
|
|
48
|
+
value: string,
|
|
49
|
+
theme: Theme,
|
|
50
|
+
config: Pick<StatuslineConfig, "extensionStatusIcons">,
|
|
51
|
+
extensionStatusIconAliases: ExtensionStatusIconAliasMap = EMPTY_EXTENSION_STATUS_ICON_ALIASES,
|
|
55
52
|
): string {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
config.extensionStatusIcons,
|
|
64
|
-
extensionStatusIconAliases,
|
|
65
|
-
);
|
|
66
|
-
const renderedText = theme.fg(textColor, text);
|
|
67
|
-
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;
|
|
68
60
|
}
|
|
69
61
|
|
|
70
62
|
function extensionStatusIcon(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
63
|
+
key: string,
|
|
64
|
+
leadingIcon: string | undefined,
|
|
65
|
+
configuredIcons: Record<string, string>,
|
|
66
|
+
extensionStatusIconAliases: ExtensionStatusIconAliasMap,
|
|
75
67
|
) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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] ?? "🔌";
|
|
87
79
|
}
|
|
88
80
|
|
|
89
|
-
function configuredNamespaceIcon(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (!match || base.length > match.baseLength) match = { baseLength: base.length, icon };
|
|
99
|
-
}
|
|
100
|
-
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;
|
|
101
90
|
}
|
|
102
91
|
|
|
103
92
|
function extensionStatusAliasesForKey(
|
|
104
|
-
|
|
105
|
-
|
|
93
|
+
key: string,
|
|
94
|
+
extensionStatusIconAliases: ExtensionStatusIconAliasMap,
|
|
106
95
|
): readonly string[] {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
96
|
+
for (const [statusBase, aliases] of extensionStatusIconAliases) {
|
|
97
|
+
if (statusKeyMatchesStatusBase(key, statusBase)) return aliases;
|
|
98
|
+
}
|
|
99
|
+
return [];
|
|
111
100
|
}
|
|
112
101
|
|
|
113
102
|
function statusKeyMatchesStatusBase(key: string, statusBase: string): boolean {
|
|
114
|
-
|
|
103
|
+
return key === statusBase || key.startsWith(`${statusBase}:`) || key.startsWith(`${statusBase}/`);
|
|
115
104
|
}
|
|
116
105
|
|
|
117
106
|
export function wrapExtensionStatusline(status: string, width: number): string[] {
|
|
118
|
-
|
|
119
|
-
|
|
107
|
+
if (!status || width <= 0) return [];
|
|
108
|
+
return wrapTextWithAnsi(status, width);
|
|
120
109
|
}
|
|
121
110
|
|
|
122
111
|
function formatDuplicateExtensionStatus(runtime: ExtensionStatusRuntime, theme: Theme): string[] {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
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}`)}`];
|
|
128
116
|
}
|
|
129
117
|
|
|
130
118
|
export function splitExtensionStatusIcon(value: string): { icon?: string; text: string } {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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 };
|
|
137
125
|
}
|
|
138
126
|
|
|
139
127
|
function isEmojiOnlyToken(value: string): boolean {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
+
);
|
|
143
131
|
}
|
|
144
132
|
|
|
145
133
|
export function extensionColor(key: string, value: string): ThemeColor {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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";
|
|
151
139
|
}
|
|
152
140
|
|
|
153
141
|
export function stripExtensionStatusPrefix(key: string, value: string): string {
|
|
154
|
-
|
|
142
|
+
return value.trim().replace(new RegExp(`^${escapeRegExp(key)}\\s*:\\s*`, "iu"), "");
|
|
155
143
|
}
|
|
156
144
|
|
|
157
145
|
export function simplifyExtensionStatusText(value: string): string {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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, " ");
|
|
165
153
|
}
|
|
166
154
|
|
|
167
155
|
function escapeRegExp(value: string): string {
|
|
168
|
-
|
|
156
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
169
157
|
}
|
|
170
158
|
|
|
171
159
|
interface InstalledExtensionPackage {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
160
|
+
packageName: string;
|
|
161
|
+
source: string;
|
|
162
|
+
identity: string;
|
|
175
163
|
}
|
|
176
164
|
|
|
177
165
|
export function readInstalledExtensionPackages(cwd: string): InstalledExtensionPackage[] {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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;
|
|
193
181
|
}
|
|
194
182
|
|
|
195
183
|
function extensionSettingsFiles(cwd: string): string[] {
|
|
196
|
-
|
|
197
|
-
existsSync(file),
|
|
198
|
-
);
|
|
184
|
+
return [join(getAgentDir(), "settings.json"), join(cwd, ".pi", "settings.json")].filter((file) => existsSync(file));
|
|
199
185
|
}
|
|
200
186
|
|
|
201
|
-
export function findDuplicateExtensions(
|
|
202
|
-
|
|
203
|
-
): string[] {
|
|
204
|
-
const sourcesByPackage = new Map<string, Set<string>>();
|
|
187
|
+
export function findDuplicateExtensions(installedPackages: readonly InstalledExtensionPackage[]): string[] {
|
|
188
|
+
const sourcesByPackage = new Map<string, Set<string>>();
|
|
205
189
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
+
}
|
|
211
195
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
196
|
+
return [...sourcesByPackage.entries()]
|
|
197
|
+
.filter(([, sources]) => sources.size > 1)
|
|
198
|
+
.map(([packageName]) => packageName.replace(/^@[^/]+\//, "").replace(/^pi-/, ""));
|
|
215
199
|
}
|
|
216
200
|
|
|
217
201
|
export function buildExtensionStatusIconAliases(
|
|
218
|
-
|
|
202
|
+
installedPackages: readonly { packageName: string; source?: string }[],
|
|
219
203
|
): Map<string, string[]> {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const aliases = new Map<string, string[]>();
|
|
239
|
-
for (const [statusBase, aliasesByPackage] of packageAliasesByStatusBase) {
|
|
240
|
-
if (aliasesByPackage.size === 1)
|
|
241
|
-
aliases.set(statusBase, [...aliasesByPackage.values()][0] ?? []);
|
|
242
|
-
}
|
|
243
|
-
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;
|
|
244
220
|
}
|
|
245
221
|
|
|
246
222
|
function extensionStatusIconAliasCandidate(
|
|
247
|
-
|
|
248
|
-
|
|
223
|
+
packageName: string,
|
|
224
|
+
source?: string,
|
|
249
225
|
): { statusBase: string; aliases: string[] } | undefined {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
+
};
|
|
259
235
|
}
|
|
260
236
|
|
|
261
237
|
function packageBaseName(packageName: string): string {
|
|
262
|
-
|
|
263
|
-
|
|
238
|
+
const slashIndex = packageName.lastIndexOf("/");
|
|
239
|
+
return slashIndex === -1 ? packageName : packageName.slice(slashIndex + 1);
|
|
264
240
|
}
|
|
265
241
|
|
|
266
242
|
function statusBaseFromPackageBase(packageBase: string): string {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
243
|
+
return packageBase.startsWith("pi-") && packageBase.length > "pi-".length
|
|
244
|
+
? packageBase.slice("pi-".length)
|
|
245
|
+
: packageBase;
|
|
270
246
|
}
|
|
271
247
|
|
|
272
248
|
function uniqueStrings(values: readonly string[]): string[] {
|
|
273
|
-
|
|
249
|
+
return [...new Set(values.filter((value) => value.length > 0))];
|
|
274
250
|
}
|
|
275
251
|
|
|
276
252
|
function readPackageSources(settingsFile: string): string[] {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
.filter((source): source is string => source !== undefined);
|
|
292
|
-
} catch {
|
|
293
|
-
return [];
|
|
294
|
-
}
|
|
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
|
+
}
|
|
295
267
|
}
|
|
296
268
|
|
|
297
269
|
function packageNameForSource(source: string, baseDirectory: string): string | undefined {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
+
}
|
|
306
278
|
}
|
|
307
279
|
|
|
308
280
|
export function npmPackageName(source: string): string {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
281
|
+
const spec = source.slice("npm:".length);
|
|
282
|
+
if (spec.startsWith("@")) return spec.split("@").slice(0, 2).join("@");
|
|
283
|
+
return spec.split("@")[0] ?? spec;
|
|
312
284
|
}
|
|
313
285
|
|
|
314
286
|
function sourceIdentity(source: string, baseDirectory: string): string {
|
|
315
|
-
|
|
316
|
-
|
|
287
|
+
if (source.startsWith("npm:")) return `npm:${npmPackageName(source)}`;
|
|
288
|
+
return resolveSourcePath(source, baseDirectory);
|
|
317
289
|
}
|
|
318
290
|
|
|
319
291
|
function resolveSourcePath(source: string, baseDirectory: string): string {
|
|
320
|
-
|
|
292
|
+
return isAbsolute(source) ? source : resolve(baseDirectory, source);
|
|
321
293
|
}
|