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}
)}
)
}