admina
Version:
Detect root/admin/sudo and execute commands as it if available
114 lines (103 loc) • 2.99 kB
text/typescript
import which from "which"
import {
execa,
execaCommand,
execaCommandSync,
execaSync,
type SyncOptions as ExecaSyncOptions,
type ExecaSyncReturnValue,
type Options as ExecaOptions,
type ExecaChildProcess,
} from "execa"
/** Detect if sudo is available */
export function hasSudo(): boolean {
return which.sync("sudo", { nothrow: true }) !== null
}
/**
* Detect if the process has root privilege on Posix.
*
* @example
*
* ```js
* import { isRoot } from "admina"
*
* console.log(isRoot())
* //=> false
* ```
*
* @returns Whether the process is running as root.
*/
export function isRoot(): boolean {
// TODO not all CI systems are root
return process.getuid?.() === 0 || Boolean(process.env.CI)
}
/** Detect if sudo is available and the user has root privileges */
export function isSudo(): boolean {
return isRoot() && hasSudo()
}
/** Prepend `sudo` to the command if sudo is available */
export function prependSudo(command: string) {
if (isSudo()) {
return `sudo ${command}`
}
return command
}
/** Default exec options `{ stdio: "inherit", shell: true }` */
export const defaultExecOptions: ExecaSyncOptions = {
stdio: "inherit",
shell: true,
}
/**
* Execute a command as root if sudo is available. Otherwise executes the command normally without sudo.
*
* @param program The program to spawn
* @param args The command arguments
* @param execOptions The options passed to `execa`. Defaults to `{ stdio: "inherit", shell: true }`
* @returns The execution result
*/
export function execRootSync(
program: string,
args: string[] = [],
execOptions: ExecaSyncOptions = defaultExecOptions,
): ExecaSyncReturnValue<string> {
if (isSudo()) {
const command = getSudoCommand(program, args)
return execaCommandSync(command, execOptions)
} else {
return execaSync(program, args, execOptions)
}
}
/**
* Asynchronously execute a command as root if sudo is available. Otherwise executes the command normally without sudo.
*
* @param program The program to spawn
* @param args The command arguments
* @param execOptions The options passed to `execa`. Defaults to `{ stdio: "inherit", shell: true }`
* @returns A promise to the execution result
*/
export function execRoot(
program: string,
args: string[] = [],
execOptions: ExecaOptions = defaultExecOptions,
): ExecaChildProcess<string> {
if (isSudo()) {
const command = getSudoCommand(program, args)
return execaCommand(command, execOptions)
} else {
return execa(program, args, execOptions)
}
}
function getSudoCommand(program: string, args: string[]) {
return `sudo ${quote([program, ...args]).join(" ")}`
}
function quote(strings: string[]) {
return strings.map((str) => (isQuoted(str) ? str : `'${str}'`))
}
function isQuoted(str: string) {
if (str.length < 2) {
return false
}
const first = str[0]
const last = str[str.length - 1]
return (first === "'" && last === "'") || (first === '"' && last === '"')
}