89 lines
2.4 KiB
TypeScript
89 lines
2.4 KiB
TypeScript
import { execSync } from 'child_process';
|
|
import type { SkillInstallerSpec } from './types.js';
|
|
|
|
export interface InstallerPlanStep {
|
|
installerType: SkillInstallerSpec['type'];
|
|
command: string;
|
|
}
|
|
|
|
export interface InstallerPlanSkip {
|
|
installerType: SkillInstallerSpec['type'];
|
|
reason: string;
|
|
}
|
|
|
|
export interface InstallerPlan {
|
|
mode: 'dry-run';
|
|
steps: InstallerPlanStep[];
|
|
skipped: InstallerPlanSkip[];
|
|
}
|
|
|
|
export interface InstallerPlanningOptions {
|
|
hasBinary?: (name: string) => boolean;
|
|
}
|
|
|
|
function hasBinaryOnPath(name: string): boolean {
|
|
const cmd = process.platform === 'win32' ? 'where' : 'which';
|
|
try {
|
|
execSync(`${cmd} ${name}`, { stdio: 'ignore' });
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export function buildInstallerPlan(
|
|
installers: SkillInstallerSpec[] | undefined,
|
|
opts: InstallerPlanningOptions = {},
|
|
): InstallerPlan {
|
|
const hasBinary = opts.hasBinary ?? hasBinaryOnPath;
|
|
const steps: InstallerPlanStep[] = [];
|
|
const skipped: InstallerPlanSkip[] = [];
|
|
|
|
for (const installer of installers ?? []) {
|
|
if (installer.type === 'brew') {
|
|
if (!hasBinary('brew')) {
|
|
skipped.push({ installerType: 'brew', reason: 'brew not available in PATH' });
|
|
continue;
|
|
}
|
|
steps.push({ installerType: 'brew', command: `brew install ${installer.packages.join(' ')}` });
|
|
continue;
|
|
}
|
|
|
|
if (installer.type === 'node') {
|
|
if (hasBinary('pnpm')) {
|
|
steps.push({ installerType: 'node', command: `pnpm add -g ${installer.packages.join(' ')}` });
|
|
continue;
|
|
}
|
|
if (hasBinary('npm')) {
|
|
steps.push({ installerType: 'node', command: `npm install -g ${installer.packages.join(' ')}` });
|
|
continue;
|
|
}
|
|
skipped.push({ installerType: 'node', reason: 'neither pnpm nor npm available in PATH' });
|
|
continue;
|
|
}
|
|
|
|
if (installer.type === 'go') {
|
|
if (!hasBinary('go')) {
|
|
skipped.push({ installerType: 'go', reason: 'go not available in PATH' });
|
|
continue;
|
|
}
|
|
for (const pkg of installer.packages) {
|
|
steps.push({ installerType: 'go', command: `go install ${pkg}@latest` });
|
|
}
|
|
continue;
|
|
}
|
|
|
|
const destination = installer.destination ?? '<default destination>';
|
|
steps.push({
|
|
installerType: 'download',
|
|
command: `download ${installer.url} -> ${destination}`,
|
|
});
|
|
}
|
|
|
|
return {
|
|
mode: 'dry-run',
|
|
steps,
|
|
skipped,
|
|
};
|
|
}
|