142 lines
4.3 KiB
TypeScript
142 lines
4.3 KiB
TypeScript
/**
|
|
* Cross-platform dependency resolution and platform detection.
|
|
* Helps select the appropriate install spec for the current OS.
|
|
*/
|
|
|
|
import { hasBinary } from "./config.js";
|
|
import type { SkillEntry, SkillInstallSpec } from "./types.js";
|
|
|
|
export type PackageManagerKind = "brew" | "apt" | "winget" | "choco" | "scoop" | "npm";
|
|
|
|
export type PlatformInfo = {
|
|
/** Node.js process.platform value */
|
|
os: NodeJS.Platform;
|
|
/** Best package manager for this platform */
|
|
preferredManager: PackageManagerKind | undefined;
|
|
/** All detected package managers available on PATH */
|
|
availableManagers: PackageManagerKind[];
|
|
};
|
|
|
|
/**
|
|
* Detect the current platform and available package managers.
|
|
*/
|
|
export function detectPlatform(): PlatformInfo {
|
|
const os = process.platform;
|
|
const availableManagers: PackageManagerKind[] = [];
|
|
|
|
// Check each package manager
|
|
if (hasBinary("brew")) availableManagers.push("brew");
|
|
if (hasBinary("apt-get") || hasBinary("apt")) availableManagers.push("apt");
|
|
if (hasBinary("winget")) availableManagers.push("winget");
|
|
if (hasBinary("choco")) availableManagers.push("choco");
|
|
if (hasBinary("scoop")) availableManagers.push("scoop");
|
|
if (hasBinary("npm")) availableManagers.push("npm");
|
|
|
|
const preferredManager = resolvePreferredManager(os, availableManagers);
|
|
return { os, preferredManager, availableManagers };
|
|
}
|
|
|
|
/**
|
|
* Select the best install spec for the current platform from a skill's install array.
|
|
* Prioritizes specs matching the preferred package manager, then falls back to
|
|
* any compatible spec (e.g., download).
|
|
*/
|
|
export function selectInstallSpec(
|
|
entry: SkillEntry,
|
|
platform: PlatformInfo,
|
|
): SkillInstallSpec | undefined {
|
|
const specs = entry.metadata?.install ?? [];
|
|
if (specs.length === 0) return undefined;
|
|
|
|
// Filter specs compatible with current OS
|
|
const compatible = specs.filter((spec) => {
|
|
if (!spec.os || spec.os.length === 0) return true;
|
|
return spec.os.includes(platform.os);
|
|
});
|
|
|
|
if (compatible.length === 0) return undefined;
|
|
|
|
// Try preferred manager first, then other available managers
|
|
const managerPriority = platform.preferredManager
|
|
? [
|
|
platform.preferredManager,
|
|
...platform.availableManagers.filter((m) => m !== platform.preferredManager),
|
|
]
|
|
: platform.availableManagers;
|
|
|
|
for (const manager of managerPriority) {
|
|
const match = compatible.find((spec) => spec.kind === manager);
|
|
if (match) return match;
|
|
}
|
|
|
|
// Fall back to universal install methods (don't require a platform package manager)
|
|
const universalKinds = ["download", "node", "go", "uv"];
|
|
const universalSpec = compatible.find((spec) => universalKinds.includes(spec.kind));
|
|
if (universalSpec) return universalSpec;
|
|
|
|
// Last resort: first compatible spec
|
|
return compatible[0];
|
|
}
|
|
|
|
/**
|
|
* Check if a specific package manager is available on the system.
|
|
*/
|
|
export function hasPackageManager(kind: PackageManagerKind): boolean {
|
|
switch (kind) {
|
|
case "brew":
|
|
return hasBinary("brew");
|
|
case "apt":
|
|
return hasBinary("apt-get") || hasBinary("apt");
|
|
case "winget":
|
|
return hasBinary("winget");
|
|
case "choco":
|
|
return hasBinary("choco");
|
|
case "scoop":
|
|
return hasBinary("scoop");
|
|
case "npm":
|
|
return hasBinary("npm");
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a human-readable name for a package manager.
|
|
*/
|
|
export function getPackageManagerLabel(kind: PackageManagerKind): string {
|
|
switch (kind) {
|
|
case "brew":
|
|
return "Homebrew";
|
|
case "apt":
|
|
return "APT (Debian/Ubuntu)";
|
|
case "winget":
|
|
return "Windows Package Manager (winget)";
|
|
case "choco":
|
|
return "Chocolatey";
|
|
case "scoop":
|
|
return "Scoop";
|
|
case "npm":
|
|
return "npm";
|
|
default:
|
|
return kind;
|
|
}
|
|
}
|
|
|
|
function resolvePreferredManager(
|
|
os: string,
|
|
available: PackageManagerKind[],
|
|
): PackageManagerKind | undefined {
|
|
// Platform-specific preferences
|
|
if (os === "darwin" && available.includes("brew")) return "brew";
|
|
if (os === "linux" && available.includes("apt")) return "apt";
|
|
if (os === "win32") {
|
|
// Windows: prefer winget > scoop > choco
|
|
if (available.includes("winget")) return "winget";
|
|
if (available.includes("scoop")) return "scoop";
|
|
if (available.includes("choco")) return "choco";
|
|
}
|
|
|
|
// Fallback to first available
|
|
return available[0];
|
|
}
|