surfer/src/utils/command-exists.ts
2024-07-06 21:17:06 +02:00

120 lines
3.6 KiB
TypeScript

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This file was originally under the MIT license, but is now under the MPL 2.0
// license. The following license notice applies to only this file
//
// The MIT License (MIT)
//
// Copyright (c) 2014 Matthew Conlen
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// Adapted from the `command-exists` node module
// https://github.com/mathisonian/command-exists
import { execSync } from 'node:child_process'
import { accessSync, constants } from 'node:fs'
import path from 'node:path'
const onWindows = process.platform == 'win32'
const fileNotExistsSync = (commandName: string): boolean => {
try {
accessSync(commandName, constants.F_OK)
return false
} catch {
return true
}
}
const localExecutableSync = (commandName: string): boolean => {
try {
accessSync(commandName, constants.F_OK | constants.X_OK)
return true
} catch {
return false
}
}
const commandExistsUnixSync = function (
commandName: string,
cleanedCommandName: string
): boolean {
if (fileNotExistsSync(commandName)) {
try {
const stdout = execSync(
'command -v ' +
cleanedCommandName +
' 2>/dev/null' +
' && { echo >&1 ' +
cleanedCommandName +
'; exit 0; }'
)
return !!stdout
} catch {
return false
}
}
return localExecutableSync(commandName)
}
const commandExistsWindowsSync = function (
commandName: string,
cleanedCommandName: string
): boolean {
// Regex from Julio from: https://stackoverflow.com/questions/51494579/regex-windows-path-validator
if (
!/^(?!(?:.*\s|.*\.|\W+)$)(?:[A-Za-z]:)?(?:[^\n"*:<>?|]+(?:\/\/|\/|\\\\|\\)?)+$/m.test(
commandName
)
) {
return false
}
try {
const stdout = execSync('where ' + cleanedCommandName, { stdio: [] })
return !!stdout
} catch {
return false
}
}
function cleanInput(toBeCleaned: string): string {
// Windows has a different cleaning process to Unix, so we should go through
// that process first
if (onWindows) {
const isPathName = /\\/.test(toBeCleaned)
if (isPathName) {
const dirname = '"' + path.dirname(toBeCleaned) + '"'
const basename = '"' + path.basename(toBeCleaned) + '"'
return `${dirname}:${basename}`
}
return `"${toBeCleaned}"`
}
// Otherwise go through the unix cleaning process
if (/[^\w/:=\\-]/.test(toBeCleaned)) {
toBeCleaned = "'" + toBeCleaned.replace(/'/g, "'\\''") + "'"
toBeCleaned = toBeCleaned
.replace(/^(?:'')+/g, '') // unduplicate single-quote at the beginning
.replace(/\\'''/g, "\\'") // remove non-escaped single-quote if there are enclosed between 2 escaped
}
return toBeCleaned
}
export function commandExistsSync(commandName: string): boolean {
const cleanedCommandName = cleanInput(commandName)
return onWindows
? commandExistsWindowsSync(commandName, cleanedCommandName)
: commandExistsUnixSync(commandName, cleanedCommandName)
}