vaporwaver-ts
Version:
TypeScript version of the Vaporwaver Python library. Vaporwaver is a Python library for generating vaporwave image art.
142 lines (122 loc) • 5.57 kB
text/typescript
import { spawn } from "child_process";
export class DependencyError extends Error {
constructor(message: string, public readonly details: any = {}) {
super(message);
this.name = 'DependencyError';
}
}
export class DependencyChecker {
private static pythonCommand = process.platform === 'win32' ? 'python' : 'python3';
private static pipCommand = process.platform === 'win32' ? 'pip' : 'pip3';
static async checkPython(minVersion = '3.7.0'): Promise<void> {
try {
const version = await this.getPythonVersion();
if (!this.isVersionSatisfied(version, minVersion)) {
throw new DependencyError(
`Python version ${minVersion} or higher is required. Found: ${version}`
);
}
} catch (error) {
if (error instanceof DependencyError) throw error;
throw new DependencyError('Python is not installed or not accessible');
}
}
static async checkPythonDependencies(): Promise<void> {
try {
const deps = await this.getInstalledPythonPackages();
const required = ['Pillow', 'glitch-this', 'opencv-python'];
const missing = required.filter(dep => !deps.some(installed =>
installed.toLowerCase().startsWith(dep.toLowerCase())
));
if (missing.length > 0) {
// Try to install missing dependencies
await this.installMissingDependencies(missing);
}
} catch (error) {
throw new DependencyError(
'Failed to verify Python dependencies',
{ originalError: error }
);
}
}
private static async getPythonVersion(): Promise<string> {
return new Promise((resolve, reject) => {
const python = spawn(this.pythonCommand, ['--version']);
let output = '';
python.stdout.on('data', (data) => { output += data; });
python.stderr.on('data', (data) => { output += data; });
python.on('close', (code) => {
if (code === 0) {
const version = output.match(/(\d+\.\d+\.\d+)/);
if (version) {
resolve(version[1]);
} else {
reject(new Error('Could not parse Python version'));
}
} else {
reject(new Error(`Python check failed with code ${code}`));
}
});
});
}
private static async getInstalledPythonPackages(): Promise<string[]> {
return new Promise((resolve, reject) => {
const pip = spawn(this.pipCommand, ['freeze']);
let output = '';
pip.stdout.on('data', (data) => { output += data; });
pip.stderr.on('data', () => { /* ignore stderr */ });
pip.on('close', (code) => {
if (code === 0) {
resolve(output.split('\n').filter(Boolean));
} else {
reject(new Error('Failed to get installed packages'));
}
});
});
}
private static async installMissingDependencies(packages: string[]): Promise<void> {
// Try different installation methods
const methods = [
{ cmd: this.pipCommand, args: ['install', '--user'] },
{ cmd: this.pipCommand, args: ['install'] },
{ cmd: this.pythonCommand, args: ['-m', 'pip', 'install', '--user'] },
{ cmd: this.pythonCommand, args: ['-m', 'pip', 'install'] }
];
for (const method of methods) {
try {
await this.tryInstallMethod(method.cmd, [...method.args, ...packages]);
return; // Installation successful
} catch (error) {
console.warn(`Installation attempt failed with method ${method.cmd}`, error);
// Continue to next method
}
}
throw new DependencyError(
'Failed to install Python dependencies. Please install manually:\n' +
`${this.pythonCommand} -m pip install ${packages.join(' ')}`
);
}
private static async tryInstallMethod(cmd: string, args: string[]): Promise<void> {
return new Promise((resolve, reject) => {
const install = spawn(cmd, args);
let output = '';
install.stdout.on('data', (data) => { output += data; });
install.stderr.on('data', (data) => { output += data; });
install.on('close', (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`Installation failed with code ${code}\n${output}`));
}
});
});
}
private static isVersionSatisfied(current: string, required: string): boolean {
const parseVersion = (v: string): number[] => v.split('.').map(Number);
const [currentMajor, currentMinor, currentPatch] = parseVersion(current);
const [reqMajor, reqMinor, reqPatch] = parseVersion(required);
if (currentMajor !== reqMajor) return currentMajor > reqMajor;
if (currentMinor !== reqMinor) return currentMinor > reqMinor;
return currentPatch >= reqPatch;
}
}