macapp: add error handling for symlink operations

Refactor the installer to do a better job handling errors when creating symlinks.
It checks if paths are valid before trying to use them, safely creates
directories if they don't exist, and clearly tells you what went wrong if
something fails. It also uses TypeScript to catch mistakes early and makes
sure paths work correctly in all cases.
This commit is contained in:
Bruce MacDonald 2024-12-05 16:17:35 -08:00
parent 2b82c5a8a1
commit 8f8aac9cd3

View File

@ -1,21 +1,35 @@
import * as fs from 'fs' import * as fs from 'fs'
import { exec as cbExec } from 'child_process' import { spawn } from 'child_process'
import * as path from 'path' import * as path from 'path'
import { promisify } from 'util'
const app = process && process.type === 'renderer' ? require('@electron/remote').app : require('electron').app const app = process && process.type === 'renderer' ? require('@electron/remote').app : require('electron').app
const ollama = app.isPackaged ? path.join(process.resourcesPath, 'ollama') : path.resolve(process.cwd(), '..', 'ollama') const ollama = app.isPackaged ? path.join(process.resourcesPath, 'ollama') : path.resolve(process.cwd(), '..', 'ollama')
const exec = promisify(cbExec)
const symlinkPath = '/usr/local/bin/ollama' const symlinkPath = '/usr/local/bin/ollama'
export function installed() { export function installed(): boolean {
return fs.existsSync(symlinkPath) && fs.readlinkSync(symlinkPath) === ollama return fs.existsSync(symlinkPath) && fs.readlinkSync(symlinkPath) === ollama
} }
export async function install() { function validPath(targetPath: string): boolean {
const command = `do shell script "mkdir -p ${path.dirname( const normalized = path.normalize(targetPath)
symlinkPath return !(/[;&|`$(){}[\]<>]/.test(normalized) || normalized.includes('..'))
)} && ln -F -s \\"${ollama}\\" \\"${symlinkPath}\\"" with administrator privileges` }
await exec(`osascript -e '${command}'`) export async function install(): Promise<void> {
if (!validPath(ollama) || !validPath(symlinkPath)) {
throw new Error('Invalid path format')
}
await fs.promises.mkdir(path.dirname(symlinkPath), { recursive: true })
.catch(err => err.code === 'EEXIST' ? null : Promise.reject(err))
const process = spawn('osascript', [
'-e',
`do shell script "ln -F -s '${path.normalize(ollama)}' '${path.normalize(symlinkPath)}'" with administrator privileges`
])
await new Promise<void>((resolve, reject) => {
process.on('error', reject)
process.on('close', code => code === 0 ? resolve() : reject(new Error(`Failed with code ${code}`)))
})
} }