import { Box, Text } from 'ink' import * as React from 'react' import { getTheme } from '../utils/theme' import { gte } from 'semver' import { useEffect, useState } from 'react' import { isAutoUpdaterDisabled } from '../utils/config' import { AutoUpdaterResult, getLatestVersion, installGlobalPackage, } from '../utils/autoUpdater.js' import { useInterval } from '../hooks/useInterval' import { logEvent } from '../services/statsig' import { MACRO } from '../constants/macros' import { PRODUCT_COMMAND } from '../constants/product' type Props = { debug: boolean isUpdating: boolean onChangeIsUpdating: (isUpdating: boolean) => void onAutoUpdaterResult: (autoUpdaterResult: AutoUpdaterResult) => void autoUpdaterResult: AutoUpdaterResult | null } export function AutoUpdater({ debug, isUpdating, onChangeIsUpdating, onAutoUpdaterResult, autoUpdaterResult, }: Props): React.ReactNode { const theme = getTheme() const [versions, setVersions] = useState<{ global?: string | null latest?: string | null }>({}) const checkForUpdates = React.useCallback(async () => { if (process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'dev') { return } if (isUpdating) { return } // Get versions const globalVersion = MACRO.VERSION const latestVersion = await getLatestVersion() const isDisabled = true //await isAutoUpdaterDisabled() setVersions({ global: globalVersion, latest: latestVersion }) // Check if update needed and perform update if ( !isDisabled && globalVersion && latestVersion && !gte(globalVersion, latestVersion) ) { const startTime = Date.now() onChangeIsUpdating(true) const installStatus = await installGlobalPackage() onChangeIsUpdating(false) if (installStatus === 'success') { logEvent('tengu_auto_updater_success', { fromVersion: globalVersion, toVersion: latestVersion, durationMs: String(Date.now() - startTime), }) } else { logEvent('tengu_auto_updater_fail', { fromVersion: globalVersion, attemptedVersion: latestVersion, status: installStatus, durationMs: String(Date.now() - startTime), }) } onAutoUpdaterResult({ version: latestVersion!, status: installStatus, }) } // Don't re-render when isUpdating changes // TODO: Find a cleaner way to do this // eslint-disable-next-line react-hooks/exhaustive-deps }, [onAutoUpdaterResult]) // Initial check useEffect(() => { // checkForUpdates() }, [checkForUpdates]) // Check every 30 minutes // useInterval(checkForUpdates, 30 * 60 * 1000) if (debug) { return ( globalVersion: {versions.global} · latestVersion:{' '} {versions.latest} ) } if (!autoUpdaterResult?.version && (!versions.global || !versions.latest)) { return null } if (!autoUpdaterResult?.version && !isUpdating) { return null } return ( {debug && ( globalVersion: {versions.global} · latestVersion:{' '} {versions.latest} )} {isUpdating && ( <> Auto-updating to v{versions.latest}… )} {autoUpdaterResult?.status === 'success' && autoUpdaterResult?.version ? ( ✓ Update installed · Restart to apply ) : null} {(autoUpdaterResult?.status === 'install_failed' || autoUpdaterResult?.status === 'no_permissions') && ( ✗ Auto-update failed · Try{' '} {PRODUCT_COMMAND} doctor or{' '} npm i -g {MACRO.PACKAGE_URL} )} ) }