Compare commits

...

1 Commits

Author SHA1 Message Date
Bruce MacDonald
8f8aac9cd3 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.
2024-12-05 16:20:43 -08:00

View File

@ -1,21 +1,35 @@
import * as fs from 'fs'
import { exec as cbExec } from 'child_process'
import { spawn } from 'child_process'
import * as path from 'path'
import { promisify } from 'util'
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 exec = promisify(cbExec)
const symlinkPath = '/usr/local/bin/ollama'
export function installed() {
export function installed(): boolean {
return fs.existsSync(symlinkPath) && fs.readlinkSync(symlinkPath) === ollama
}
export async function install() {
const command = `do shell script "mkdir -p ${path.dirname(
symlinkPath
)} && ln -F -s \\"${ollama}\\" \\"${symlinkPath}\\"" with administrator privileges`
await exec(`osascript -e '${command}'`)
function validPath(targetPath: string): boolean {
const normalized = path.normalize(targetPath)
return !(/[;&|`$(){}[\]<>]/.test(normalized) || normalized.includes('..'))
}
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}`)))
})
}