()
| 160 | } |
| 161 | |
| 162 | async function pickInstallTarget(): Promise<InstallTarget> { |
| 163 | const onPath = pathDirsOnPath() |
| 164 | const home = os.homedir() |
| 165 | const candidates = candidateDirs() |
| 166 | |
| 167 | // Pass 1: a candidate that is BOTH on PATH AND user-writable. |
| 168 | // This is the no-sudo, no-shell-edit happy path. |
| 169 | for (const dir of candidates) { |
| 170 | if (!onPath.has(dir)) continue |
| 171 | if (await isWritableDir(dir)) { |
| 172 | return { |
| 173 | linkPath: path.join(dir, CLI_NAME), |
| 174 | onPath: true, |
| 175 | requiresSudo: false, |
| 176 | pathHint: null |
| 177 | } |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | // Pass 2: a user-writable candidate even if it's not on PATH yet. |
| 182 | // We can usually create ~/.local/bin or ~/bin on the fly. Tell the |
| 183 | // user how to put it on PATH after install. |
| 184 | const userLocal = path.join(home, '.local', 'bin') |
| 185 | const userHomeBin = path.join(home, 'bin') |
| 186 | for (const dir of [userLocal, userHomeBin]) { |
| 187 | try { |
| 188 | await fsp.mkdir(dir, { recursive: true }) |
| 189 | } catch { |
| 190 | continue |
| 191 | } |
| 192 | if (await isWritableDir(dir)) { |
| 193 | return { |
| 194 | linkPath: path.join(dir, CLI_NAME), |
| 195 | onPath: false, |
| 196 | requiresSudo: false, |
| 197 | pathHint: pathExportSnippet(dir) |
| 198 | } |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | // Pass 3: fall back to /usr/local/bin with a sudo prompt. This is |
| 203 | // the historical install location and still on PATH almost |
| 204 | // everywhere, so the binary will be callable immediately. |
| 205 | const target = path.join(SUDO_FALLBACK_DIR, CLI_NAME) |
| 206 | return { |
| 207 | linkPath: target, |
| 208 | onPath: onPath.has(SUDO_FALLBACK_DIR), |
| 209 | requiresSudo: true, |
| 210 | pathHint: onPath.has(SUDO_FALLBACK_DIR) ? null : pathExportSnippet(SUDO_FALLBACK_DIR) |
| 211 | } |
| 212 | } |
| 213 | |
| 214 | function pathExportSnippet(dir: string): string { |
| 215 | return `echo 'export PATH="${dir}:$PATH"' >> ~/.zshrc && source ~/.zshrc` |
no test coverage detected