|
1 | | -import { OS } from 'codify-schemas'; |
| 1 | +import { LinuxDistro, OS } from 'codify-schemas'; |
| 2 | +import * as fsSync from 'node:fs'; |
| 3 | +import * as fs from 'node:fs/promises'; |
2 | 4 | import os from 'node:os'; |
3 | 5 | import path from 'node:path'; |
4 | 6 |
|
5 | | -import { getPty, SpawnStatus } from '../pty/index.js'; |
| 7 | +import { SpawnStatus, getPty } from '../pty/index.js'; |
6 | 8 |
|
7 | 9 | export function isDebug(): boolean { |
8 | 10 | return process.env.DEBUG != null && process.env.DEBUG.includes('codify'); // TODO: replace with debug library |
@@ -175,6 +177,163 @@ export const Utils = { |
175 | 177 | const lines = pathQuery.split(':'); |
176 | 178 | return lines.includes(directory); |
177 | 179 | }, |
| 180 | + |
| 181 | + async assertBrewInstalled(): Promise<void> { |
| 182 | + const $ = getPty(); |
| 183 | + const brewCheck = await $.spawnSafe('which brew', { interactive: true }); |
| 184 | + if (brewCheck.status === SpawnStatus.ERROR) { |
| 185 | + throw new Error( |
| 186 | + `Homebrew is not installed. Cannot install git-lfs without Homebrew installed. |
| 187 | +
|
| 188 | +Brew can be installed using Codify: |
| 189 | +{ |
| 190 | + "type": "homebrew", |
| 191 | +}` |
| 192 | + ); |
| 193 | + } |
| 194 | + }, |
| 195 | + |
| 196 | + /** |
| 197 | + * Installs a package via the system package manager. This will use Homebrew on macOS and apt on Ubuntu/Debian or dnf on Fedora. |
| 198 | + * @param packageName |
| 199 | + */ |
| 200 | + async installViaPkgMgr(packageName: string): Promise<void> { |
| 201 | + const $ = getPty(); |
| 202 | + |
| 203 | + if (Utils.isMacOS()) { |
| 204 | + await this.assertBrewInstalled(); |
| 205 | + await $.spawn(`brew install ${packageName}`, { interactive: true, env: { HOMEBREW_NO_AUTO_UPDATE: 1 } }); |
| 206 | + } |
| 207 | + |
| 208 | + if (Utils.isLinux()) { |
| 209 | + const isAptInstalled = await $.spawnSafe('which apt'); |
| 210 | + if (isAptInstalled.status === SpawnStatus.SUCCESS) { |
| 211 | + await $.spawn('apt-get update', { requiresRoot: true }); |
| 212 | + const { status, data } = await $.spawnSafe(`apt-get -y install ${packageName}`, { |
| 213 | + requiresRoot: true, |
| 214 | + env: { DEBIAN_FRONTEND: 'noninteractive' } |
| 215 | + }); |
| 216 | + |
| 217 | + if (status === SpawnStatus.ERROR && data.includes('E: dpkg was interrupted, you must manually run \'sudo dpkg --configure -a\' to correct the problem.')) { |
| 218 | + await $.spawn('dpkg --configure -a', { requiresRoot: true }); |
| 219 | + await $.spawn(`apt-get -y install ${packageName}`, { |
| 220 | + requiresRoot: true, |
| 221 | + env: { DEBIAN_FRONTEND: 'noninteractive' } |
| 222 | + }); |
| 223 | + |
| 224 | + return; |
| 225 | + } |
| 226 | + |
| 227 | + if (status === SpawnStatus.ERROR) { |
| 228 | + throw new Error(`Failed to install package ${packageName} via apt: ${data}`); |
| 229 | + } |
| 230 | + } |
| 231 | + |
| 232 | + const isDnfInstalled = await $.spawnSafe('which dnf'); |
| 233 | + if (isDnfInstalled.status === SpawnStatus.SUCCESS) { |
| 234 | + await $.spawn('dnf update', { requiresRoot: true }); |
| 235 | + await $.spawn(`dnf install ${packageName} -y`, { requiresRoot: true }); |
| 236 | + } |
| 237 | + |
| 238 | + const isYumInstalled = await $.spawnSafe('which yum'); |
| 239 | + if (isYumInstalled.status === SpawnStatus.SUCCESS) { |
| 240 | + await $.spawn('yum update', { requiresRoot: true }); |
| 241 | + await $.spawn(`yum install ${packageName} -y`, { requiresRoot: true }); |
| 242 | + } |
| 243 | + |
| 244 | + const isPacmanInstalled = await $.spawnSafe('which pacman'); |
| 245 | + if (isPacmanInstalled.status === SpawnStatus.SUCCESS) { |
| 246 | + await $.spawn('pacman -Syu', { requiresRoot: true }); |
| 247 | + await $.spawn(`pacman -S ${packageName} --noconfirm`, { requiresRoot: true }); |
| 248 | + } |
| 249 | + |
| 250 | + } |
| 251 | + }, |
| 252 | + |
| 253 | + async uninstallViaPkgMgr(packageName: string): Promise<boolean> { |
| 254 | + const $ = getPty(); |
| 255 | + |
| 256 | + if (Utils.isMacOS()) { |
| 257 | + await this.assertBrewInstalled(); |
| 258 | + const { status } = await $.spawnSafe(`brew uninstall --zap ${packageName}`, { |
| 259 | + interactive: true, |
| 260 | + env: { HOMEBREW_NO_AUTO_UPDATE: 1 } |
| 261 | + }); |
| 262 | + return status === SpawnStatus.SUCCESS; |
| 263 | + } |
| 264 | + |
| 265 | + if (Utils.isLinux()) { |
| 266 | + const isAptInstalled = await $.spawnSafe('which apt'); |
| 267 | + if (isAptInstalled.status === SpawnStatus.SUCCESS) { |
| 268 | + const { status } = await $.spawnSafe(`apt-get autoremove -y --purge ${packageName}`, { |
| 269 | + requiresRoot: true, |
| 270 | + env: { DEBIAN_FRONTEND: 'noninteractive' } |
| 271 | + }); |
| 272 | + return status === SpawnStatus.SUCCESS; |
| 273 | + } |
| 274 | + |
| 275 | + const isDnfInstalled = await $.spawnSafe('which dnf'); |
| 276 | + if (isDnfInstalled.status === SpawnStatus.SUCCESS) { |
| 277 | + const { status } = await $.spawnSafe(`dnf autoremove ${packageName} -y`, { requiresRoot: true }); |
| 278 | + return status === SpawnStatus.SUCCESS; |
| 279 | + } |
| 280 | + |
| 281 | + const isYumInstalled = await $.spawnSafe('which yum'); |
| 282 | + if (isYumInstalled.status === SpawnStatus.SUCCESS) { |
| 283 | + const { status } = await $.spawnSafe(`yum autoremove ${packageName} -y`, { requiresRoot: true }); |
| 284 | + return status === SpawnStatus.SUCCESS; |
| 285 | + } |
| 286 | + |
| 287 | + return false; |
| 288 | + } |
| 289 | + |
| 290 | + return false; |
| 291 | + }, |
| 292 | + |
| 293 | + async getLinuxDistro(): Promise<LinuxDistro | undefined> { |
| 294 | + const osRelease = await fs.readFile('/etc/os-release', 'utf8'); |
| 295 | + const lines = osRelease.split('\n'); |
| 296 | + for (const line of lines) { |
| 297 | + if (line.startsWith('ID=')) { |
| 298 | + const distroId = line.slice(3).trim().replaceAll('"', ''); |
| 299 | + return Object.values(LinuxDistro).includes(distroId as LinuxDistro) ? distroId as LinuxDistro : undefined; |
| 300 | + } |
| 301 | + } |
| 302 | + |
| 303 | + return undefined; |
| 304 | + }, |
| 305 | + |
| 306 | + async isUbuntu(): Promise<boolean> { |
| 307 | + return (await this.getLinuxDistro()) === LinuxDistro.UBUNTU; |
| 308 | + }, |
| 309 | + |
| 310 | + async isDebian(): Promise<boolean> { |
| 311 | + return (await this.getLinuxDistro()) === LinuxDistro.DEBIAN; |
| 312 | + }, |
| 313 | + |
| 314 | + async isArch(): Promise<boolean> { |
| 315 | + return (await this.getLinuxDistro()) === LinuxDistro.ARCH; |
| 316 | + }, |
| 317 | + |
| 318 | + async isCentOS(): Promise<boolean> { |
| 319 | + return (await this.getLinuxDistro()) === LinuxDistro.CENTOS; |
| 320 | + }, |
| 321 | + |
| 322 | + async isFedora(): Promise<boolean> { |
| 323 | + return (await this.getLinuxDistro()) === LinuxDistro.FEDORA; |
| 324 | + }, |
| 325 | + |
| 326 | + async isRHEL(): Promise<boolean> { |
| 327 | + return (await this.getLinuxDistro()) === LinuxDistro.RHEL; |
| 328 | + }, |
| 329 | + |
| 330 | + isDebianBased(): boolean { |
| 331 | + return fsSync.existsSync('/etc/debian_version'); |
| 332 | + }, |
| 333 | + |
| 334 | + isRedhatBased(): boolean { |
| 335 | + return fsSync.existsSync('/etc/redhat-release'); |
| 336 | + } |
178 | 337 | }; |
179 | 338 |
|
180 | 339 |
|
|
0 commit comments