diff --git a/.gitignore b/.gitignore index 1a841eec3..48f19fa83 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,6 @@ leak_report *.dist *.build /electron/bins/ + +# logs +*.log \ No newline at end of file diff --git a/electron/constants/paths.js b/electron/constants/paths.js new file mode 100644 index 000000000..32c7325cc --- /dev/null +++ b/electron/constants/paths.js @@ -0,0 +1,27 @@ +const os = require('os'); +const path = require('path'); +const fs = require('fs'); + +const dotOperateDirectory = + process.env.NODE_ENV === 'production' + ? path.join(os.homedir(), '.operate') + : '.operate'; + +// Create operate directory if it doesn't exist +if (!fs.existsSync(dotOperateDirectory)) { + fs.mkdirSync(dotOperateDirectory); +} + +const paths = { + dotOperateDirectory, + servicesDir: path.join(dotOperateDirectory, 'services'), + venvDir: path.join(dotOperateDirectory, 'venv'), + tempDir: path.join(dotOperateDirectory, 'temp'), + versionFile: path.join(dotOperateDirectory, 'version.txt'), + cliLogFile: path.join(dotOperateDirectory, 'cli.log'), + electronLogFile: path.join(dotOperateDirectory, 'electron.log'), + nextLogFile: path.join(dotOperateDirectory, 'next.log'), + osPearlTempDir: path.join(os.tmpdir(), 'pearl'), +}; + +module.exports = { paths }; diff --git a/electron/install.js b/electron/install.js index 188c4a223..a69475325 100644 --- a/electron/install.js +++ b/electron/install.js @@ -1,33 +1,19 @@ // Installation helpers. const fs = require('fs'); const os = require('os'); -const path = require('path'); const sudo = require('sudo-prompt'); const process = require('process'); const axios = require('axios'); const { spawnSync } = require('child_process'); +const { paths } = require('./constants/paths'); + /** * current version of the pearl release * - use "" (nothing as a suffix) for latest release candidate, for example "0.1.0rc26" * - use "alpha" for alpha release, for example "0.1.0rc26-alpha" */ const OlasMiddlewareVersion = '0.1.0rc64'; -const OperateDirectory = path.join(os.homedir(), '.operate'); - -// Create operate directory if it doesn't exist -if (!fs.existsSync(OperateDirectory)) { - fs.mkdirSync(OperateDirectory); -} - -const paths = { - OperateDirectory, - VenvDir: path.join(OperateDirectory, '.operate', 'venv'), - TempDir: path.join(OperateDirectory, '.operate', 'temp'), - VersionFile: path.join(OperateDirectory, '.operate', 'version.txt'), - LogFile: path.join(OperateDirectory, '.operate', 'logs.txt'), - OperateInstallationLog: path.join(os.homedir(), 'operate.log'), -}; const Env = { ...process.env, @@ -152,7 +138,7 @@ async function downloadFile(url, dest) { async function installTendermintUnix() { const cwd = process.cwd(); - process.chdir(paths.TempDir); + process.chdir(paths.tempDir); console.log( appendInstallationLog( @@ -164,7 +150,7 @@ async function installTendermintUnix() { console.log( appendInstallationLog(`Downloading ${url}, might take a while...`), ); - await downloadFile(url, `${paths.TempDir}/tendermint.tar.gz`); + await downloadFile(url, `${paths.tempDir}/tendermint.tar.gz`); console.log(appendInstallationLog(`Installing tendermint binary`)); runCmdUnix('tar', ['-xvf', 'tendermint.tar.gz']); @@ -226,7 +212,7 @@ function installOperateCli(path) { } return new Promise((resolve, _reject) => { fs.copyFile( - `${OperateDirectory}/venv/bin/operate`, + `${paths.dotOperateDirectory}/venv/bin/operate`, installPath, function (error, _stdout, _stderr) { resolve(!error); @@ -247,15 +233,15 @@ function createDirectory(path) { } function writeVersion() { - fs.writeFileSync(paths.VersionFile, OlasMiddlewareVersion); + fs.writeFileSync(paths.versionFile, OlasMiddlewareVersion); } function versionBumpRequired() { - if (!fs.existsSync(paths.VersionFile)) { + if (!fs.existsSync(paths.versionFile)) { return true; } const olasMiddlewareVersionInFile = fs - .readFileSync(paths.VersionFile) + .readFileSync(paths.versionFile) .toString(); return olasMiddlewareVersionInFile != OlasMiddlewareVersion; } @@ -279,8 +265,8 @@ function removeInstallationLogFile() { async function setupDarwin(ipcChannel) { removeInstallationLogFile(); console.log(appendInstallationLog('Creating required directories')); - await createDirectory(`${OperateDirectory}`); - await createDirectory(`${OperateDirectory}/temp`); + await createDirectory(`${paths.dotOperateDirectory}`); + await createDirectory(`${paths.dotOperateDirectory}/temp`); console.log(appendInstallationLog('Checking tendermint installation')); if (!isTendermintInstalledUnix()) { @@ -310,19 +296,19 @@ async function setupUbuntu(ipcChannel) { if (!isPythonInstalledUbuntu()) { ipcChannel.send('response', 'Installing Pearl Daemon'); console.log(appendInstallationLog('Installing Python')); - await installPythonUbuntu(OperateDirectory); + await installPythonUbuntu(paths.dotOperateDirectory); } console.log(appendInstallationLog('Checking git installation')); if (!isGitInstalledUbuntu()) { ipcChannel.send('response', 'Installing Pearl Daemon'); console.log(appendInstallationLog('Installing git')); - await installGitUbuntu(OperateDirectory); + await installGitUbuntu(paths.dotOperateDirectory); } console.log(appendInstallationLog('Creating required directories')); - await createDirectory(`${OperateDirectory}`); - await createDirectory(`${OperateDirectory}/temp`); + await createDirectory(`${paths.dotOperateDirectory}`); + await createDirectory(`${paths.dotOperateDirectory}/temp`); console.log(appendInstallationLog('Checking tendermint installation')); if (!isTendermintInstalledUnix()) { @@ -331,13 +317,13 @@ async function setupUbuntu(ipcChannel) { await installTendermintUnix(); } - if (!fs.existsSync(paths.VenvDir)) { + if (!fs.existsSync(paths.venvDir)) { ipcChannel.send('response', 'Installing Pearl Daemon'); console.log(appendInstallationLog('Creating virtual environment')); - createVirtualEnvUnix(paths.VenvDir); + createVirtualEnvUnix(paths.venvDir); console.log(appendInstallationLog('Installing pearl backend')); - installOperatePackageUnix(OperateDirectory); + installOperatePackageUnix(paths.dotOperateDirectory); } console.log(appendInstallationLog('Checking if upgrade is required')); @@ -347,13 +333,13 @@ async function setupUbuntu(ipcChannel) { `Upgrading pearl daemon to ${OlasMiddlewareVersion}`, ), ); - reInstallOperatePackageUnix(OperateDirectory); + reInstallOperatePackageUnix(paths.dotOperateDirectory); writeVersion(); removeLogFile(); } - if (!fs.existsSync(`${OperateDirectory}/venv/bin/operate`)) { - reInstallOperatePackageUnix(OperateDirectory); + if (!fs.existsSync(`${paths.dotOperateDirectory}/venv/bin/operate`)) { + reInstallOperatePackageUnix(paths.dotOperateDirectory); } console.log(appendInstallationLog('Installing pearl CLI')); @@ -364,5 +350,4 @@ module.exports = { setupDarwin, setupUbuntu, Env, - paths, }; diff --git a/electron/logger.js b/electron/logger.js new file mode 100644 index 000000000..444d0f926 --- /dev/null +++ b/electron/logger.js @@ -0,0 +1,73 @@ +const winston = require('winston'); +const { format } = require('logform'); +const { paths } = require('./constants/paths'); + +const { combine, timestamp, printf } = format; + +const logFormat = printf(({ level, message, timestamp }) => { + return `${timestamp} ${level}: ${message}`; +}); + +const customLevels = { + levels: { + error: 0, + warn: 1, + info: 2, + next: 3, + cli: 4, + electron: 5, + }, + colors: { + error: 'red', + warn: 'yellow', + info: 'blue', + cli: 'green bold underline', + electron: 'magenta bold underline', + next: 'cyan bold underline', + }, +}; + +// Custom filter for specific levels, otherwise higher levels will include lower levels +const levelFilter = (level) => + format((info, _opts) => { + return info.level === level ? info : false; + })(); + +const logger = winston.createLogger({ + levels: customLevels.levels, + transports: [ + new winston.transports.Console({ + level: 'electron', // Set to the highest level so it captures everything. + format: combine(winston.format.colorize(), timestamp(), logFormat), + }), + new winston.transports.File({ + filename: 'cli.log', + dirname: paths.dotOperateDirectory, + level: 'cli', + format: combine(levelFilter('cli'), timestamp(), logFormat), + maxFiles: 1, + maxsize: 1024 * 1024 * 10, + }), + new winston.transports.File({ + filename: 'electron.log', + dirname: paths.dotOperateDirectory, + level: 'electron', + format: combine(levelFilter('electron'), timestamp(), logFormat), + maxFiles: 1, + maxsize: 1024 * 1024 * 10, + }), + new winston.transports.File({ + filename: 'next.log', + dirname: paths.dotOperateDirectory, + level: 'next', + format: combine(levelFilter('next'), timestamp(), logFormat), + maxFiles: 1, + maxsize: 1024 * 1024 * 10, + }), + ], + format: combine(timestamp(), logFormat), +}); + +winston.addColors(customLevels.colors); + +module.exports = { logger }; diff --git a/electron/main.js b/electron/main.js index 7e4a582fa..c7edf4263 100644 --- a/electron/main.js +++ b/electron/main.js @@ -1,8 +1,5 @@ const dotenv = require('dotenv'); -const console = require('electron-log/main'); // Supports log levels and file logging -console.initialize(); - const { app, BrowserWindow, @@ -22,13 +19,15 @@ const http = require('http'); const AdmZip = require('adm-zip'); const { TRAY_ICONS, TRAY_ICONS_PATHS } = require('./icons'); -const { paths, Env } = require('./install'); +const { Env } = require('./install'); +const { paths } = require('./constants/paths'); const { killProcesses } = require('./processes'); const { isPortAvailable, findAvailablePort } = require('./ports'); const { PORT_RANGE, isWindows, isMac } = require('./constants'); const { macUpdater } = require('./update'); const { setupStoreIpc } = require('./store'); +const { logger } = require('./logger'); // Configure environment variables dotenv.config(); @@ -77,7 +76,7 @@ async function beforeQuit() { try { await killProcesses(operateDaemonPid); } catch (e) { - console.error(e); + logger.electron(e); } } @@ -85,7 +84,7 @@ async function beforeQuit() { try { await killProcesses(nextAppProcessPid); } catch (e) { - console.error(e); + logger.electron(e); } } @@ -274,29 +273,18 @@ const createMainWindow = () => { }; async function launchDaemon() { - function appendLog(data) { - fs.appendFileSync( - `${paths.OperateDirectory}/logs.txt`, - data.trim() + '\n', - { - encoding: 'utf-8', - }, - ); - return data; - } - // Free up backend port if already occupied try { await fetch(`http://localhost:${appConfig.ports.prod.operate}/api`); - console.log('Killing backend server!'); + logger.electron('Killing backend server!'); let endpoint = fs - .readFileSync(`${paths.OperateDirectory}/operate.kill`) + .readFileSync(`${paths.dotOperateDirectory}/operate.kill`) .toString() .trim(); await fetch(`http://localhost:${appConfig.ports.prod.operate}/${endpoint}`); } catch (err) { - console.log('Backend not running!'); + logger.electron('Backend not running!'); } const check = new Promise(function (resolve, _reject) { @@ -308,7 +296,7 @@ async function launchDaemon() { [ 'daemon', `--port=${appConfig.ports.prod.operate}`, - `--home=${paths.OperateDirectory}`, + `--home=${paths.dotOperateDirectory}`, ], { env: Env }, ); @@ -330,10 +318,10 @@ async function launchDaemon() { ) { resolve({ running: false, error: 'Port already in use' }); } - console.log(appendLog(data.toString().trim())); + logger.cli(data.toString().trim()); }); operateDaemon.stdout.on('data', (data) => { - console.log(appendLog(data.toString().trim())); + logger.cli(data.toString().trim()); }); }); @@ -359,10 +347,10 @@ async function launchDaemonDev() { ) { resolve({ running: false, error: 'Port already in use' }); } - console.log(data.toString().trim()); + logger.cli(data.toString().trim()); }); operateDaemon.stdout.on('data', (data) => { - console.log(data.toString().trim()); + logger.cli(data.toString().trim()); }); }); return await check; @@ -392,7 +380,7 @@ async function launchNextApp() { }); server.listen(appConfig.ports.prod.next, (err) => { if (err) throw err; - console.log( + logger.next( `> Next server running on http://localhost:${appConfig.ports.prod.next}`, ); }); @@ -413,7 +401,7 @@ async function launchNextAppDev() { ); nextAppProcessPid = nextAppProcess.pid; nextAppProcess.stdout.on('data', (data) => { - console.log(data.toString().trim()); + logger.next(data.toString().trim()); resolve(); }); }); @@ -438,7 +426,7 @@ ipcMain.on('check', async function (event, _argument) { // }); // }); } catch (e) { - console.error(e); + logger.electron(e); } // Setup @@ -508,7 +496,7 @@ ipcMain.on('check', async function (event, _argument) { createTray(); splashWindow.destroy(); } catch (e) { - console.log(e); + logger.electron(e); new Notification({ title: 'Error', body: e, @@ -543,7 +531,7 @@ macUpdater.on('update-downloaded', () => { // PROCESS SPECIFIC EVENTS (HANDLES NON-GRACEFUL TERMINATION) process.on('uncaughtException', (error) => { - console.error('Uncaught Exception:', error); + logger.electron('Uncaught Exception:', error); // Clean up your child processes here beforeQuit().then(() => { process.exit(1); // Exit with a failure code @@ -552,7 +540,7 @@ process.on('uncaughtException', (error) => { ['SIGINT', 'SIGTERM'].forEach((signal) => { process.on(signal, () => { - console.log(`Received ${signal}. Cleaning up...`); + logger.electron(`Received ${signal}. Cleaning up...`); beforeQuit().then(() => { process.exit(0); }); @@ -564,16 +552,35 @@ ipcMain.on('open-path', (_, filePath) => { shell.openPath(filePath); }); -function getSanitizedLogs({ name, filePath, data }) { +/** + * Sanitizes logs by replacing usernames in the log data with asterisks. + * If a file path is provided, it reads the log data from the file and sanitizes it. + * If the file path does not exist, it returns null. + * If no file path is provided, it sanitizes the provided data directly. + * The sanitized log data is then written to the destination path. + * @param {Object} options - The options for sanitizing logs. + * @param {string} options.name - The name of the log file. + * @param {string} options.filePath - The file path to read the log data from. + * @param {string} options.data - The log data to sanitize if no file path is provided. + * @param {string} options.destPath - The destination path where the logs should be stored after sanitization. + * @returns {string|null} - The file path of the sanitized log data, or null if the file path does not exist. + */ +function sanitizeLogs({ + name, + filePath, + data, + destPath = paths.osPearlTempDir, +}) { if (filePath && !fs.existsSync(filePath)) return null; const logs = filePath ? fs.readFileSync(filePath, 'utf-8') : data; - const tempDir = os.tmpdir(); - const usernameRegex = /\/Users\/([^/]+)/g; - const sanitizedData = logs.replace(usernameRegex, '/Users/*****'); + const usernameRegex = /\/(Users|home)\/([^/]+)/g; + const sanitizedData = logs.replace(usernameRegex, '/$1/*****'); + const sanitizedLogsFilePath = path.join(destPath, name); + + if (!fs.existsSync(destPath)) fs.mkdirSync(destPath); - const sanitizedLogsFilePath = path.join(tempDir, name); fs.writeFileSync(sanitizedLogsFilePath, sanitizedData); return sanitizedLogsFilePath; @@ -581,21 +588,20 @@ function getSanitizedLogs({ name, filePath, data }) { // EXPORT LOGS ipcMain.handle('save-logs', async (_, data) => { - // version.txt - const versionFile = paths.VersionFile; - // logs.txt - const logFile = getSanitizedLogs({ - name: 'log.txt', - filePath: paths.LogFile, + sanitizeLogs({ + name: 'cli.log', + filePath: paths.cliLogFile, }); - // operate.log - const installationLog = getSanitizedLogs({ - name: 'installation_log.txt', - filePath: paths.OperateInstallationLog, + sanitizeLogs({ + name: 'next.log', + filePath: paths.nextLogFile, }); - const tempDir = os.tmpdir(); + sanitizeLogs({ + name: 'electron.log', + filePath: paths.electronLogFile, + }); // OS info const osInfo = ` @@ -606,43 +612,68 @@ ipcMain.handle('save-logs', async (_, data) => { Total Memory: ${os.totalmem()} Free Memory: ${os.freemem()} `; - const osInfoFilePath = path.join(tempDir, 'os_info.txt'); + const osInfoFilePath = path.join(paths.osPearlTempDir, 'os_info.txt'); fs.writeFileSync(osInfoFilePath, osInfo); // Persistent store - let storeFilePath; - if (data.store) { - storeFilePath = path.join(tempDir, 'store.txt'); - fs.writeFileSync(storeFilePath, JSON.stringify(data.store, null, 2)); - } + if (data.store) + sanitizeLogs({ + name: 'store.txt', + data: JSON.stringify(data.store, null, 2), + }); // Other debug data: balances, addresses, etc. - let debugDataFilePath; - if (data.debugData) { - debugDataFilePath = getSanitizedLogs({ + if (data.debugData) + sanitizeLogs({ name: 'debug_data.txt', data: JSON.stringify(data.debugData, null, 2), }); + + // Agent logs + try { + fs.readdirSync(paths.servicesDir).map((serviceDirName) => { + const servicePath = path.join(paths.servicesDir, serviceDirName); + if (!fs.existsSync(servicePath)) return; + if (!fs.statSync(servicePath).isDirectory()) return; + + const agentLogFilePath = path.join( + servicePath, + 'deployment', + 'agent', + 'log.txt', + ); + if (!fs.existsSync(agentLogFilePath)) return; + + return sanitizeLogs({ + name: `${serviceDirName}_agent.log`, + filePath: agentLogFilePath, + }); + }); + } catch (e) { + logger.electron(e); } // Create a zip archive const zip = new AdmZip(); - fs.existsSync(versionFile) && zip.addLocalFile(versionFile); - fs.existsSync(logFile) && zip.addLocalFile(logFile); - fs.existsSync(installationLog) && zip.addLocalFile(installationLog); - fs.existsSync(osInfoFilePath) && zip.addLocalFile(osInfoFilePath); - fs.existsSync(storeFilePath) && zip.addLocalFile(storeFilePath); - fs.existsSync(debugDataFilePath) && zip.addLocalFile(debugDataFilePath); + fs.readdirSync(paths.osPearlTempDir).forEach((file) => { + const filePath = path.join(paths.osPearlTempDir, file); + if (!fs.existsSync(filePath)) return; + if (fs.statSync(filePath).isDirectory()) return; + + zip.addLocalFile(filePath); + }); // Show save dialog const { filePath } = await dialog.showSaveDialog({ title: 'Save Logs', - defaultPath: path.join(os.homedir(), 'pearl_logs.zip'), + defaultPath: path.join( + os.homedir(), + `pearl_logs_${new Date(Date.now()).toISOString()}-${app.getVersion()}.zip`, + ), filters: [{ name: 'Zip Files', extensions: ['zip'] }], }); let result; - if (filePath) { // Write the zip file to the selected path zip.writeZip(filePath); @@ -652,11 +683,11 @@ ipcMain.handle('save-logs', async (_, data) => { } // Remove temporary files - fs.existsSync(logFile) && fs.unlinkSync(logFile); - fs.existsSync(installationLog) && fs.unlinkSync(installationLog); - fs.existsSync(osInfo) && fs.unlinkSync(osInfoFilePath); - fs.existsSync(storeFilePath) && fs.unlinkSync(storeFilePath); - fs.existsSync(debugDataFilePath) && fs.unlinkSync(debugDataFilePath); + fs.existsSync(paths.osPearlTempDir) && + fs.rmSync(paths.osPearlTempDir, { + recursive: true, + force: true, + }); return result; }); diff --git a/electron/scripts.js b/electron/scripts.js deleted file mode 100644 index d3f388aa7..000000000 --- a/electron/scripts.js +++ /dev/null @@ -1,1098 +0,0 @@ -const BrewScript = "# We don't need return codes for \"$(command)\", only stdout is needed.\n" + -"# Allow `[[ -n \"$(command)\" ]]`, `func \"$(command)\"`, pipes, etc.\n" + -"# shellcheck disable=SC2312\n" + -"\n" + -"set -u\n" + -"\n" + -"abort() {\n" + -" printf \"%s\\n\" \"$@\" >&2\n" + -" exit 1\n" + -"}\n" + -"\n" + -"# Fail fast with a concise message when not using bash\n" + -"# Single brackets are needed here for POSIX compatibility\n" + -"# shellcheck disable=SC2292\n" + -"if [ -z \"${BASH_VERSION:-}\" ]\n" + -"then\n" + -" abort \"Bash is required to interpret this script.\"\n" + -"fi\n" + -"\n" + -"# Check if script is run with force-interactive mode in CI\n" + -"if [[ -n \"${CI-}\" && -n \"${INTERACTIVE-}\" ]]\n" + -"then\n" + -" abort \"Cannot run force-interactive mode in CI.\"\n" + -"fi\n" + -"\n" + -"# Check if both `INTERACTIVE` and `NONINTERACTIVE` are set\n" + -"# Always use single-quoted strings with `exp` expressions\n" + -"# shellcheck disable=SC2016\n" + -"if [[ -n \"${INTERACTIVE-}\" && -n \"${NONINTERACTIVE-}\" ]]\n" + -"then\n" + -" abort 'Both `$INTERACTIVE` and `$NONINTERACTIVE` are set. Please unset at least one variable and try again.'\n" + -"fi\n" + -"\n" + -"# Check if script is run in POSIX mode\n" + -"if [[ -n \"${POSIXLY_CORRECT+1}\" ]]\n" + -"then\n" + -" abort 'Bash must not run in POSIX mode. Please unset POSIXLY_CORRECT and try again.'\n" + -"fi\n" + -"\n" + -"usage() {\n" + -" cat <${tty_bold} %s${tty_reset}\\n\" \"$(shell_join \"$@\")\"\n" + -"}\n" + -"\n" + -"warn() {\n" + -" printf \"${tty_red}Warning${tty_reset}: %s\\n\" \"$(chomp \"$1\")\" >&2\n" + -"}\n" + -"\n" + -"# Check if script is run non-interactively (e.g. CI)\n" + -"# If it is run non-interactively we should not prompt for passwords.\n" + -"# Always use single-quoted strings with `exp` expressions\n" + -"# shellcheck disable=SC2016\n" + -"if [[ -z \"${NONINTERACTIVE-}\" ]]\n" + -"then\n" + -" if [[ -n \"${CI-}\" ]]\n" + -" then\n" + -" warn 'Running in non-interactive mode because `$CI` is set.'\n" + -" NONINTERACTIVE=1\n" + -" elif [[ ! -t 0 ]]\n" + -" then\n" + -" if [[ -z \"${INTERACTIVE-}\" ]]\n" + -" then\n" + -" warn 'Running in non-interactive mode because `stdin` is not a TTY.'\n" + -" NONINTERACTIVE=1\n" + -" else\n" + -" warn 'Running in interactive mode despite `stdin` not being a TTY because `$INTERACTIVE` is set.'\n" + -" fi\n" + -" fi\n" + -"else\n" + -" ohai 'Running in non-interactive mode because `$NONINTERACTIVE` is set.'\n" + -"fi\n" + -"\n" + -"# USER isn't always set so provide a fall back for the installer and subprocesses.\n" + -"if [[ -z \"${USER-}\" ]]\n" + -"then\n" + -" USER=\"$(chomp \"$(id -un)\")\"\n" + -" export USER\n" + -"fi\n" + -"\n" + -"# First check OS.\n" + -"OS=\"$(uname)\"\n" + -"if [[ \"${OS}\" == \"Linux\" ]]\n" + -"then\n" + -" HOMEBREW_ON_LINUX=1\n" + -"elif [[ \"${OS}\" == \"Darwin\" ]]\n" + -"then\n" + -" HOMEBREW_ON_MACOS=1\n" + -"else\n" + -" abort \"Homebrew is only supported on macOS and Linux.\"\n" + -"fi\n" + -"\n" + -"# Required installation paths. To install elsewhere (which is unsupported)\n" + -"# you can untar https://github.com/Homebrew/brew/tarball/master\n" + -"# anywhere you like.\n" + -"if [[ -n \"${HOMEBREW_ON_MACOS-}\" ]]\n" + -"then\n" + -" UNAME_MACHINE=\"$(/usr/bin/uname -m)\"\n" + -"\n" + -" if [[ \"${UNAME_MACHINE}\" == \"arm64\" ]]\n" + -" then\n" + -" # On ARM macOS, this script installs to /opt/homebrew only\n" + -" HOMEBREW_PREFIX=\"/opt/homebrew\"\n" + -" HOMEBREW_REPOSITORY=\"${HOMEBREW_PREFIX}\"\n" + -" else\n" + -" # On Intel macOS, this script installs to /usr/local only\n" + -" HOMEBREW_PREFIX=\"/usr/local\"\n" + -" HOMEBREW_REPOSITORY=\"${HOMEBREW_PREFIX}/Homebrew\"\n" + -" fi\n" + -" HOMEBREW_CACHE=\"${HOME}/Library/Caches/Homebrew\"\n" + -"\n" + -" STAT_PRINTF=(\"/usr/bin/stat\" \"-f\")\n" + -" PERMISSION_FORMAT=\"%A\"\n" + -" CHOWN=(\"/usr/sbin/chown\")\n" + -" CHGRP=(\"/usr/bin/chgrp\")\n" + -" GROUP=\"admin\"\n" + -" TOUCH=(\"/usr/bin/touch\")\n" + -" INSTALL=(\"/usr/bin/install\" -d -o \"root\" -g \"wheel\" -m \"0755\")\n" + -"else\n" + -" UNAME_MACHINE=\"$(uname -m)\"\n" + -"\n" + -" # On Linux, this script installs to /home/linuxbrew/.linuxbrew only\n" + -" HOMEBREW_PREFIX=\"/home/linuxbrew/.linuxbrew\"\n" + -" HOMEBREW_REPOSITORY=\"${HOMEBREW_PREFIX}/Homebrew\"\n" + -" HOMEBREW_CACHE=\"${HOME}/.cache/Homebrew\"\n" + -"\n" + -" STAT_PRINTF=(\"/usr/bin/stat\" \"--printf\")\n" + -" PERMISSION_FORMAT=\"%a\"\n" + -" CHOWN=(\"/bin/chown\")\n" + -" CHGRP=(\"/bin/chgrp\")\n" + -" GROUP=\"$(id -gn)\"\n" + -" TOUCH=(\"/bin/touch\")\n" + -" INSTALL=(\"/usr/bin/install\" -d -o \"${USER}\" -g \"${GROUP}\" -m \"0755\")\n" + -"fi\n" + -"CHMOD=(\"/bin/chmod\")\n" + -"MKDIR=(\"/bin/mkdir\" \"-p\")\n" + -"HOMEBREW_BREW_DEFAULT_GIT_REMOTE=\"https://github.com/Homebrew/brew\"\n" + -"HOMEBREW_CORE_DEFAULT_GIT_REMOTE=\"https://github.com/Homebrew/homebrew-core\"\n" + -"\n" + -"# Use remote URLs of Homebrew repositories from environment if set.\n" + -"HOMEBREW_BREW_GIT_REMOTE=\"${HOMEBREW_BREW_GIT_REMOTE:-\"${HOMEBREW_BREW_DEFAULT_GIT_REMOTE}\"}\"\n" + -"HOMEBREW_CORE_GIT_REMOTE=\"${HOMEBREW_CORE_GIT_REMOTE:-\"${HOMEBREW_CORE_DEFAULT_GIT_REMOTE}\"}\"\n" + -"# The URLs with and without the '.git' suffix are the same Git remote. Do not prompt.\n" + -"if [[ \"${HOMEBREW_BREW_GIT_REMOTE}\" == \"${HOMEBREW_BREW_DEFAULT_GIT_REMOTE}.git\" ]]\n" + -"then\n" + -" HOMEBREW_BREW_GIT_REMOTE=\"${HOMEBREW_BREW_DEFAULT_GIT_REMOTE}\"\n" + -"fi\n" + -"if [[ \"${HOMEBREW_CORE_GIT_REMOTE}\" == \"${HOMEBREW_CORE_DEFAULT_GIT_REMOTE}.git\" ]]\n" + -"then\n" + -" HOMEBREW_CORE_GIT_REMOTE=\"${HOMEBREW_CORE_DEFAULT_GIT_REMOTE}\"\n" + -"fi\n" + -"export HOMEBREW_{BREW,CORE}_GIT_REMOTE\n" + -"\n" + -"# TODO: bump version when new macOS is released or announced\n" + -"MACOS_NEWEST_UNSUPPORTED=\"15.0\"\n" + -"# TODO: bump version when new macOS is released\n" + -"MACOS_OLDEST_SUPPORTED=\"12.0\"\n" + -"\n" + -"# For Homebrew on Linux\n" + -"REQUIRED_RUBY_VERSION=2.6 # https://github.com/Homebrew/brew/pull/6556\n" + -"REQUIRED_GLIBC_VERSION=2.13 # https://docs.brew.sh/Homebrew-on-Linux#requirements\n" + -"REQUIRED_CURL_VERSION=7.41.0 # HOMEBREW_MINIMUM_CURL_VERSION in brew.sh in Homebrew/brew\n" + -"REQUIRED_GIT_VERSION=2.7.0 # HOMEBREW_MINIMUM_GIT_VERSION in brew.sh in Homebrew/brew\n" + -"\n" + -"# no analytics during installation\n" + -"export HOMEBREW_NO_ANALYTICS_THIS_RUN=1\n" + -"export HOMEBREW_NO_ANALYTICS_MESSAGE_OUTPUT=1\n" + -"\n" + -"unset HAVE_SUDO_ACCESS # unset this from the environment\n" + -"\n" + -"have_sudo_access() {\n" + -" if [[ ! -x \"/usr/bin/sudo\" ]]\n" + -" then\n" + -" return 1\n" + -" fi\n" + -"\n" + -" local -a SUDO=(\"/usr/bin/sudo\")\n" + -" if [[ -n \"${SUDO_ASKPASS-}\" ]]\n" + -" then\n" + -" SUDO+=(\"-A\")\n" + -" elif [[ -n \"${NONINTERACTIVE-}\" ]]\n" + -" then\n" + -" SUDO+=(\"-n\")\n" + -" fi\n" + -"\n" + -" if [[ -z \"${HAVE_SUDO_ACCESS-}\" ]]\n" + -" then\n" + -" if [[ -n \"${NONINTERACTIVE-}\" ]]\n" + -" then\n" + -" \"${SUDO[@]}\" -l mkdir &>/dev/null\n" + -" else\n" + -" \"${SUDO[@]}\" -v && \"${SUDO[@]}\" -l mkdir &>/dev/null\n" + -" fi\n" + -" HAVE_SUDO_ACCESS=\"$?\"\n" + -" fi\n" + -"\n" + -" if [[ -n \"${HOMEBREW_ON_MACOS-}\" ]] && [[ \"${HAVE_SUDO_ACCESS}\" -ne 0 ]]\n" + -" then\n" + -" abort \"Need sudo access on macOS (e.g. the user ${USER} needs to be an Administrator)!\"\n" + -" fi\n" + -"\n" + -" return \"${HAVE_SUDO_ACCESS}\"\n" + -"}\n" + -"\n" + -"execute() {\n" + -" if ! \"$@\"\n" + -" then\n" + -" abort \"$(printf \"Failed during: %s\" \"$(shell_join \"$@\")\")\"\n" + -" fi\n" + -"}\n" + -"\n" + -"execute_sudo() {\n" + -" local -a args=(\"$@\")\n" + -" if [[ \"${EUID:-${UID}}\" != \"0\" ]] && have_sudo_access\n" + -" then\n" + -" if [[ -n \"${SUDO_ASKPASS-}\" ]]\n" + -" then\n" + -" args=(\"-A\" \"${args[@]}\")\n" + -" fi\n" + -" ohai \"/usr/bin/sudo\" \"${args[@]}\"\n" + -" execute \"/usr/bin/sudo\" \"${args[@]}\"\n" + -" else\n" + -" ohai \"${args[@]}\"\n" + -" execute \"${args[@]}\"\n" + -" fi\n" + -"}\n" + -"\n" + -"getc() {\n" + -" local save_state\n" + -" save_state=\"$(/bin/stty -g)\"\n" + -" /bin/stty raw -echo\n" + -" IFS='' read -r -n 1 -d '' \"$@\"\n" + -" /bin/stty \"${save_state}\"\n" + -"}\n" + -"\n" + -"ring_bell() {\n" + -" # Use the shell's audible bell.\n" + -" if [[ -t 1 ]]\n" + -" then\n" + -" printf \"\\a\"\n" + -" fi\n" + -"}\n" + -"\n" + -"wait_for_user() {\n" + -" local c\n" + -" echo\n" + -" echo \"Press ${tty_bold}RETURN${tty_reset}/${tty_bold}ENTER${tty_reset} to continue or any other key to abort:\"\n" + -" getc c\n" + -" if ! [[ \"${c}\" == $'\\r' || \"${c}\" == $'\\n' ]]\n" + -" then\n" + -" exit 1\n" + -" fi\n" + -"}\n" + -"\n" + -"major_minor() {\n" + -" echo \"${1%%.*}.$(\n" + -" x=\"${1#*.}\"\n" + -" echo \"${x%%.*}\"\n" + -" )\"\n" + -"}\n" + -"\n" + -"version_gt() {\n" + -" [[ \"${1%.*}\" -gt \"${2%.*}\" ]] || [[ \"${1%.*}\" -eq \"${2%.*}\" && \"${1#*.}\" -gt \"${2#*.}\" ]]\n" + -"}\n" + -"version_ge() {\n" + -" [[ \"${1%.*}\" -gt \"${2%.*}\" ]] || [[ \"${1%.*}\" -eq \"${2%.*}\" && \"${1#*.}\" -ge \"${2#*.}\" ]]\n" + -"}\n" + -"version_lt() {\n" + -" [[ \"${1%.*}\" -lt \"${2%.*}\" ]] || [[ \"${1%.*}\" -eq \"${2%.*}\" && \"${1#*.}\" -lt \"${2#*.}\" ]]\n" + -"}\n" + -"\n" + -"check_run_command_as_root() {\n" + -" [[ \"${EUID:-${UID}}\" == \"0\" ]] || return\n" + -"\n" + -" # Allow Azure Pipelines/GitHub Actions/Docker/Concourse/Kubernetes to do everything as root (as it's normal there)\n" + -" [[ -f /.dockerenv ]] && return\n" + -" [[ -f /run/.containerenv ]] && return\n" + -" [[ -f /proc/1/cgroup ]] && grep -E \"azpl_job|actions_job|docker|garden|kubepods\" -q /proc/1/cgroup && return\n" + -"\n" + -" abort \"Don't run this as root!\"\n" + -"}\n" + -"\n" + -"should_install_command_line_tools() {\n" + -" if [[ -n \"${HOMEBREW_ON_LINUX-}\" ]]\n" + -" then\n" + -" return 1\n" + -" fi\n" + -"\n" + -" if version_gt \"${macos_version}\" \"10.13\"\n" + -" then\n" + -" ! [[ -e \"/Library/Developer/CommandLineTools/usr/bin/git\" ]]\n" + -" else\n" + -" ! [[ -e \"/Library/Developer/CommandLineTools/usr/bin/git\" ]] ||\n" + -" ! [[ -e \"/usr/include/iconv.h\" ]]\n" + -" fi\n" + -"}\n" + -"\n" + -"get_permission() {\n" + -" \"${STAT_PRINTF[@]}\" \"${PERMISSION_FORMAT}\" \"$1\"\n" + -"}\n" + -"\n" + -"user_only_chmod() {\n" + -" [[ -d \"$1\" ]] && [[ \"$(get_permission \"$1\")\" != 75[0145] ]]\n" + -"}\n" + -"\n" + -"exists_but_not_writable() {\n" + -" [[ -e \"$1\" ]] && ! [[ -r \"$1\" && -w \"$1\" && -x \"$1\" ]]\n" + -"}\n" + -"\n" + -"get_owner() {\n" + -" \"${STAT_PRINTF[@]}\" \"%u\" \"$1\"\n" + -"}\n" + -"\n" + -"file_not_owned() {\n" + -" [[ \"$(get_owner \"$1\")\" != \"$(id -u)\" ]]\n" + -"}\n" + -"\n" + -"get_group() {\n" + -" \"${STAT_PRINTF[@]}\" \"%g\" \"$1\"\n" + -"}\n" + -"\n" + -"file_not_grpowned() {\n" + -" [[ \" $(id -G \"${USER}\") \" != *\" $(get_group \"$1\") \"* ]]\n" + -"}\n" + -"\n" + -"# Please sync with 'test_ruby()' in 'Library/Homebrew/utils/ruby.sh' from the Homebrew/brew repository.\n" + -"test_ruby() {\n" + -" if [[ ! -x \"$1\" ]]\n" + -" then\n" + -" return 1\n" + -" fi\n" + -"\n" + -" \"$1\" --enable-frozen-string-literal --disable=gems,did_you_mean,rubyopt -rrubygems -e \\\n" + -" \"abort if Gem::Version.new(RUBY_VERSION.to_s.dup).to_s.split('.').first(2) != \\\n" + -" Gem::Version.new('${REQUIRED_RUBY_VERSION}').to_s.split('.').first(2)\" 2>/dev/null\n" + -"}\n" + -"\n" + -"test_curl() {\n" + -" if [[ ! -x \"$1\" ]]\n" + -" then\n" + -" return 1\n" + -" fi\n" + -"\n" + -" local curl_version_output curl_name_and_version\n" + -" curl_version_output=\"$(\"$1\" --version 2>/dev/null)\"\n" + -" curl_name_and_version=\"${curl_version_output%% (*}\"\n" + -" version_ge \"$(major_minor \"${curl_name_and_version##* }\")\" \"$(major_minor \"${REQUIRED_CURL_VERSION}\")\"\n" + -"}\n" + -"\n" + -"test_git() {\n" + -" if [[ ! -x \"$1\" ]]\n" + -" then\n" + -" return 1\n" + -" fi\n" + -"\n" + -" local git_version_output\n" + -" git_version_output=\"$(\"$1\" --version 2>/dev/null)\"\n" + -" if [[ \"${git_version_output}\" =~ \"git version \"([^ ]*).* ]]\n" + -" then\n" + -" version_ge \"$(major_minor \"${BASH_REMATCH[1]}\")\" \"$(major_minor \"${REQUIRED_GIT_VERSION}\")\"\n" + -" else\n" + -" abort \"Unexpected Git version: '${git_version_output}'!\"\n" + -" fi\n" + -"}\n" + -"\n" + -"# Search for the given executable in PATH (avoids a dependency on the `which` command)\n" + -"which() {\n" + -" # Alias to Bash built-in command `type -P`\n" + -" type -P \"$@\"\n" + -"}\n" + -"\n" + -"# Search PATH for the specified program that satisfies Homebrew requirements\n" + -"# function which is set above\n" + -"# shellcheck disable=SC2230\n" + -"find_tool() {\n" + -" if [[ $# -ne 1 ]]\n" + -" then\n" + -" return 1\n" + -" fi\n" + -"\n" + -" local executable\n" + -" while read -r executable\n" + -" do\n" + -" if [[ \"${executable}\" != /* ]]\n" + -" then\n" + -" warn \"Ignoring ${executable} (relative paths don't work)\"\n" + -" elif \"test_$1\" \"${executable}\"\n" + -" then\n" + -" echo \"${executable}\"\n" + -" break\n" + -" fi\n" + -" done < <(which -a \"$1\")\n" + -"}\n" + -"\n" + -"no_usable_ruby() {\n" + -" [[ -z \"$(find_tool ruby)\" ]]\n" + -"}\n" + -"\n" + -"outdated_glibc() {\n" + -" local glibc_version\n" + -" glibc_version=\"$(ldd --version | head -n1 | grep -o '[0-9.]*$' | grep -o '^[0-9]\\\+\\\.[0-9]\\\+')\"\n" + -" version_lt \"${glibc_version}\" \"${REQUIRED_GLIBC_VERSION}\"\n" + -"}\n" + -"\n" + -"if [[ -n \"${HOMEBREW_ON_LINUX-}\" ]] && no_usable_ruby && outdated_glibc\n" + -"then\n" + -" abort \"$(\n" + -" cat </dev/null\n" + -"then\n" + -" trap '/usr/bin/sudo -k' EXIT\n" + -"fi\n" + -"\n" + -"# Things can fail later if `pwd` doesn't exist.\n" + -"# Also sudo prints a warning message for no good reason\n" + -"cd \"/usr\" || exit 1\n" + -"\n" + -"####################################################################### script\n" + -"\n" + -"# shellcheck disable=SC2016\n" + -"ohai 'Checking for `sudo` access (which may request your password)...'\n" + -"\n" + -"if [[ -n \"${HOMEBREW_ON_MACOS-}\" ]]\n" + -"then\n" + -" [[ \"${EUID:-${UID}}\" == \"0\" ]] || have_sudo_access\n" + -"elif ! [[ -w \"${HOMEBREW_PREFIX}\" ]] &&\n" + -" ! [[ -w \"/home/linuxbrew\" ]] &&\n" + -" ! [[ -w \"/home\" ]] &&\n" + -" ! have_sudo_access\n" + -"then\n" + -" abort \"$(\n" + -" cat <&1)\" && [[ \"${output}\" == *\"license\"* ]]\n" + -"then\n" + -" abort \"$(\n" + -" cat </dev/null\n" + -"then\n" + -" abort \"$(\n" + -" cat </dev/null || return\n" + -"\n" + -" # we do it in four steps to avoid merge errors when reinstalling\n" + -" execute \"${USABLE_GIT}\" \"-c\" \"init.defaultBranch=master\" \"init\" \"--quiet\"\n" + -"\n" + -" # \"git remote add\" will fail if the remote is defined in the global config\n" + -" execute \"${USABLE_GIT}\" \"config\" \"remote.origin.url\" \"${HOMEBREW_BREW_GIT_REMOTE}\"\n" + -" execute \"${USABLE_GIT}\" \"config\" \"remote.origin.fetch\" \"+refs/heads/*:refs/remotes/origin/*\"\n" + -"\n" + -" # ensure we don't munge line endings on checkout\n" + -" execute \"${USABLE_GIT}\" \"config\" \"--bool\" \"core.autocrlf\" \"false\"\n" + -"\n" + -" # make sure symlinks are saved as-is\n" + -" execute \"${USABLE_GIT}\" \"config\" \"--bool\" \"core.symlinks\" \"true\"\n" + -"\n" + -" execute \"${USABLE_GIT}\" \"fetch\" \"--force\" \"origin\"\n" + -" execute \"${USABLE_GIT}\" \"fetch\" \"--force\" \"--tags\" \"origin\"\n" + -" execute \"${USABLE_GIT}\" \"remote\" \"set-head\" \"origin\" \"--auto\" >/dev/null\n" + -"\n" + -" LATEST_GIT_TAG=\"$(\"${USABLE_GIT}\" tag --list --sort=\"-version:refname\" | head -n1)\"\n" + -" if [[ -z \"${LATEST_GIT_TAG}\" ]]\n" + -" then\n" + -" abort \"Failed to query latest Homebrew/brew Git tag.\"\n" + -" fi\n" + -" execute \"${USABLE_GIT}\" \"checkout\" \"--force\" \"-B\" \"stable\" \"${LATEST_GIT_TAG}\"\n" + -"\n" + -" if [[ \"${HOMEBREW_REPOSITORY}\" != \"${HOMEBREW_PREFIX}\" ]]\n" + -" then\n" + -" if [[ \"${HOMEBREW_REPOSITORY}\" == \"${HOMEBREW_PREFIX}/Homebrew\" ]]\n" + -" then\n" + -" execute \"ln\" \"-sf\" \"../Homebrew/bin/brew\" \"${HOMEBREW_PREFIX}/bin/brew\"\n" + -" else\n" + -" abort \"The Homebrew/brew repository should be placed in the Homebrew prefix directory.\"\n" + -" fi\n" + -" fi\n" + -"\n" + -" if [[ -n \"${HOMEBREW_NO_INSTALL_FROM_API-}\" && ! -d \"${HOMEBREW_CORE}\" ]]\n" + -" then\n" + -" # Always use single-quoted strings with `exp` expressions\n" + -" # shellcheck disable=SC2016\n" + -" ohai 'Tapping homebrew/core because `$HOMEBREW_NO_INSTALL_FROM_API` is set.'\n" + -" (\n" + -" execute \"${MKDIR[@]}\" \"${HOMEBREW_CORE}\"\n" + -" cd \"${HOMEBREW_CORE}\" >/dev/null || return\n" + -"\n" + -" execute \"${USABLE_GIT}\" \"-c\" \"init.defaultBranch=master\" \"init\" \"--quiet\"\n" + -" execute \"${USABLE_GIT}\" \"config\" \"remote.origin.url\" \"${HOMEBREW_CORE_GIT_REMOTE}\"\n" + -" execute \"${USABLE_GIT}\" \"config\" \"remote.origin.fetch\" \"+refs/heads/*:refs/remotes/origin/*\"\n" + -" execute \"${USABLE_GIT}\" \"config\" \"--bool\" \"core.autocrlf\" \"false\"\n" + -" execute \"${USABLE_GIT}\" \"config\" \"--bool\" \"core.symlinks\" \"true\"\n" + -" execute \"${USABLE_GIT}\" \"fetch\" \"--force\" \"origin\" \"refs/heads/master:refs/remotes/origin/master\"\n" + -" execute \"${USABLE_GIT}\" \"remote\" \"set-head\" \"origin\" \"--auto\" >/dev/null\n" + -" execute \"${USABLE_GIT}\" \"reset\" \"--hard\" \"origin/master\"\n" + -"\n" + -" cd \"${HOMEBREW_REPOSITORY}\" >/dev/null || return\n" + -" ) || exit 1\n" + -" fi\n" + -"\n" + -" execute \"${HOMEBREW_PREFIX}/bin/brew\" \"update\" \"--force\" \"--quiet\"\n" + -") || exit 1\n" + -"\n" + -"if [[ \":${PATH}:\" != *\":${HOMEBREW_PREFIX}/bin:\"* ]]\n" + -"then\n" + -" warn \"${HOMEBREW_PREFIX}/bin is not in your PATH.\n" + -" Instructions on how to configure your shell for Homebrew\n" + -" can be found in the 'Next steps' section below.\"\n" + -"fi\n" + -"\n" + -"ohai \"Installation successful!\"\n" + -"echo\n" + -"\n" + -"ring_bell\n" + -"\n" + -"# Use an extra newline and bold to avoid this being missed.\n" + -"ohai \"Homebrew has enabled anonymous aggregate formulae and cask analytics.\"\n" + -"echo \"$(\n" + -" cat </dev/null || return\n" + -" execute \"${USABLE_GIT}\" \"config\" \"--replace-all\" \"homebrew.analyticsmessage\" \"true\"\n" + -" execute \"${USABLE_GIT}\" \"config\" \"--replace-all\" \"homebrew.caskanalyticsmessage\" \"true\"\n" + -") || exit 1\n" + -"\n" + -"ohai \"Next steps:\"\n" + -"case \"${SHELL}\" in\n" + -" */bash*)\n" + -" if [[ -n \"${HOMEBREW_ON_LINUX-}\" ]]\n" + -" then\n" + -" shell_rcfile=\"${HOME}/.bashrc\"\n" + -" else\n" + -" shell_rcfile=\"${HOME}/.bash_profile\"\n" + -" fi\n" + -" ;;\n" + -" */zsh*)\n" + -" if [[ -n \"${HOMEBREW_ON_LINUX-}\" ]]\n" + -" then\n" + -" shell_rcfile=\"${ZDOTDIR:-\"${HOME}\"}/.zshrc\"\n" + -" else\n" + -" shell_rcfile=\"${ZDOTDIR:-\"${HOME}\"}/.zprofile\"\n" + -" fi\n" + -" ;;\n" + -" */fish*)\n" + -" shell_rcfile=\"${HOME}/.config/fish/config.fish\"\n" + -" ;;\n" + -" *)\n" + -" shell_rcfile=\"${ENV:-\"${HOME}/.profile\"}\"\n" + -" ;;\n" + -"esac\n" + -"\n" + -"if grep -qs \"eval \\\"\\$(${HOMEBREW_PREFIX}/bin/brew shellenv)\\\"\" \"${shell_rcfile}\"\n" + -"then\n" + -" if ! [[ -x \"$(command -v brew)\" ]]\n" + -" then\n" + -" cat <> ${shell_rcfile}\n" + -" eval \"\\$(${HOMEBREW_PREFIX}/bin/brew shellenv)\"\n" + -"EOS\n" + -"fi\n" + -"\n" + -"if [[ -n \"${non_default_repos}\" ]]\n" + -"then\n" + -" plural=\"\"\n" + -" if [[ \"${#additional_shellenv_commands[@]}\" -gt 1 ]]\n" + -" then\n" + -" plural=\"s\"\n" + -" fi\n" + -" printf -- \"- Run these commands in your terminal to add the non-default Git remote%s for %s:\\n\" \"${plural}\" \"${non_default_repos}\"\n" + -" printf \" echo '# Set PATH, MANPATH, etc., for Homebrew.' >> %s\\n\" \"${shell_rcfile}\"\n" + -" printf \" echo '%s' >> ${shell_rcfile}\\n\" \"${additional_shellenv_commands[@]}\"\n" + -" printf \" %s\\n\" \"${additional_shellenv_commands[@]}\"\n" + -"fi\n" + -"\n" + -"if [[ -n \"${HOMEBREW_ON_LINUX-}\" ]]\n" + -"then\n" + -" echo \"- Install Homebrew's dependencies if you have sudo access:\"\n" + -"\n" + -" if [[ -x \"$(command -v apt-get)\" ]]\n" + -" then\n" + -" echo \" sudo apt-get install build-essential\"\n" + -" elif [[ -x \"$(command -v yum)\" ]]\n" + -" then\n" + -" echo \" sudo yum groupinstall 'Development Tools'\"\n" + -" elif [[ -x \"$(command -v pacman)\" ]]\n" + -" then\n" + -" echo \" sudo pacman -S base-devel\"\n" + -" elif [[ -x \"$(command -v apk)\" ]]\n" + -" then\n" + -" echo \" sudo apk add build-base\"\n" + -" fi\n" + -"\n" + -" cat <= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -4831,6 +4928,13 @@ signal-exit@^4.0.1: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== + dependencies: + is-arrayish "^0.3.1" + simple-update-notifier@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb" @@ -4902,6 +5006,11 @@ sprintf-js@^1.1.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== +stack-trace@0.0.x: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" + integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== + stacktrace-parser@^0.1.10: version "0.1.10" resolved "https://registry.yarnpkg.com/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz#29fb0cae4e0d0b85155879402857a1639eb6051a" @@ -5080,6 +5189,11 @@ temp-file@^3.4.0: async-exit-hook "^2.0.1" fs-extra "^10.0.0" +text-hex@1.0.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" + integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== + text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -5141,6 +5255,11 @@ tree-kill@^1.2.2: resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== +triple-beam@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.4.1.tgz#6fde70271dc6e5d73ca0c3b24e2d92afb7441984" + integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg== + truncate-utf8-bytes@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b" @@ -5297,6 +5416,32 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +winston-transport@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.7.0.tgz#e302e6889e6ccb7f383b926df6936a5b781bd1f0" + integrity sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg== + dependencies: + logform "^2.3.2" + readable-stream "^3.6.0" + triple-beam "^1.3.0" + +winston@^3.13.0: + version "3.13.0" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.13.0.tgz#e76c0d722f78e04838158c61adc1287201de7ce3" + integrity sha512-rwidmA1w3SE4j0E5MuIufFhyJPBDG7Nu71RkZor1p2+qHvJSZ9GYDA81AyleQcZbh/+V6HjeBdfnTZJm9rSeQQ== + dependencies: + "@colors/colors" "^1.6.0" + "@dabh/diagnostics" "^2.0.2" + async "^3.2.3" + is-stream "^2.0.0" + logform "^2.4.0" + one-time "^1.0.0" + readable-stream "^3.4.0" + safe-stable-stringify "^2.3.1" + stack-trace "0.0.x" + triple-beam "^1.3.0" + winston-transport "^4.7.0" + word-wrap@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"